diff options
Diffstat (limited to 'platform')
43 files changed, 708 insertions, 730 deletions
diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h index 48b581958b..d7322deb81 100644 --- a/platform/android/api/java_class_wrapper.h +++ b/platform/android/api/java_class_wrapper.h @@ -163,7 +163,7 @@ class JavaClass : public Reference { bool _call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error, Variant &ret); friend class JavaClassWrapper; - Map<StringName, List<MethodInfo> > methods; + Map<StringName, List<MethodInfo>> methods; jclass _class; #endif @@ -198,7 +198,7 @@ class JavaClassWrapper : public Object { GDCLASS(JavaClassWrapper, Object); #ifdef ANDROID_ENABLED - Map<String, Ref<JavaClass> > class_cache; + Map<String, Ref<JavaClass>> class_cache; friend class JavaClass; jclass activityClass; jmethodID findClass; diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 5e37f538e9..1a3bb77670 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -71,6 +71,7 @@ android { packagingOptions { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' + doNotStrip '**/*.so' } // Both signing and zip-aligning will be done at export time diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index 976a5bda99..865b61956c 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -25,7 +25,7 @@ ext { sconsExt = org.gradle.internal.os.OperatingSystem.current().isWindows() ? ".bat" : "" supportedAbis = ["armv7", "arm64v8", "x86", "x86_64"] - supportedTargets = ['release': "release", 'debug': "release_debug"] + supportedTargets = ["release", "debug"] // Used by gradle to specify which architecture to build for by default when running `./gradlew build`. // This command is usually used by Android Studio. @@ -136,14 +136,14 @@ task zipCustomBuild(type: Zip) { */ task generateGodotTemplates(type: GradleBuild) { // We exclude these gradle tasks so we can run the scons command manually. - for (String buildType : supportedTargets.keySet()) { + for (String buildType : supportedTargets) { startParameter.excludedTaskNames += ":lib:" + getSconsTaskName(buildType) } tasks = ["copyGodotPaymentPluginToAppModule"] // Only build the apks and aar files for which we have native shared libraries. - for (String target : supportedTargets.keySet()) { + for (String target : supportedTargets) { File targetLibs = new File("lib/libs/" + target) if (targetLibs != null && targetLibs.isDirectory() diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index ca8aaf8af0..062f91e08e 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -26,6 +26,7 @@ android { packagingOptions { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' + doNotStrip '**/*.so' } sourceSets { @@ -56,7 +57,7 @@ android { // files is only setup for editing support. gradle.startParameter.excludedTaskNames += taskPrefix + "externalNativeBuild" + buildType - def releaseTarget = supportedTargets[buildType.toLowerCase()] + def releaseTarget = buildType.toLowerCase() if (releaseTarget == null || releaseTarget == "") { throw new GradleException("Invalid build type: " + buildType) } diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index 7db4aa6597..4e605f9950 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -95,7 +95,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Locale; import org.godotengine.godot.input.GodotEditText; -import org.godotengine.godot.payments.PaymentsManager; import org.godotengine.godot.plugin.GodotPlugin; import org.godotengine.godot.plugin.GodotPluginRegistry; import org.godotengine.godot.utils.GodotNetUtils; @@ -174,21 +173,17 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } public ResultCallback result_callback; - private PaymentsManager mPaymentsManager = null; - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == PaymentsManager.REQUEST_CODE_FOR_PURCHASE) { - mPaymentsManager.processPurchaseResponse(resultCode, data); - } else if (result_callback != null) { + if (result_callback != null) { result_callback.callback(requestCode, resultCode, data); result_callback = null; - }; + } for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { plugin.onMainActivityResult(requestCode, resultCode, data); } - }; + } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { @@ -445,8 +440,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe result_callback = null; - mPaymentsManager = PaymentsManager.createManager(this).initService(); - godot_initialized = true; } @@ -603,7 +596,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe @Override protected void onDestroy() { - if (mPaymentsManager != null) mPaymentsManager.destroy(); for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { plugin.onMainDestroy(); } @@ -938,10 +930,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe return true; } - public PaymentsManager getPaymentsManager() { - return mPaymentsManager; - } - public boolean requestPermission(String p_name) { return PermissionsUtil.requestPermission(p_name, this); } diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java index 8d9b5461a1..18f2d57661 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java @@ -110,8 +110,13 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene @Override public void run() { for (int i = 0; i < count; ++i) { - GodotLib.key(0, 0, newChars[i], true); - GodotLib.key(0, 0, newChars[i], false); + int key = newChars[i]; + if (key == '\n') { + // Return keys are handled through action events + continue; + } + GodotLib.key(0, 0, key, true); + GodotLib.key(0, 0, key, false); } } }); @@ -134,8 +139,13 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene }); } - if (pActionID == EditorInfo.IME_ACTION_DONE) { + if (pActionID == EditorInfo.IME_NULL) { + // Enter key has been pressed + GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, true); + GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, false); + this.mView.requestFocus(); + return true; } return false; } diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/GodotPaymentInterface.java b/platform/android/java/lib/src/org/godotengine/godot/payments/GodotPaymentInterface.java deleted file mode 100644 index 6ac7338b30..0000000000 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/GodotPaymentInterface.java +++ /dev/null @@ -1,97 +0,0 @@ -/*************************************************************************/ -/* GodotPaymentInterface.java */ -/*************************************************************************/ -/* 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. */ -/*************************************************************************/ - -package org.godotengine.godot.payments; - -public interface GodotPaymentInterface { - void purchase(String sku, String transactionId); - - void consumeUnconsumedPurchases(); - - String getSignature(); - - void callbackSuccess(String ticket, String signature, String sku); - - void callbackSuccessProductMassConsumed(String ticket, String signature, String sku); - - void callbackSuccessNoUnconsumedPurchases(); - - void callbackFailConsume(String message); - - void callbackFail(String message); - - void callbackCancel(); - - void callbackAlreadyOwned(String sku); - - int getPurchaseCallbackId(); - - void setPurchaseCallbackId(int purchaseCallbackId); - - String getPurchaseValidationUrlPrefix(); - - void setPurchaseValidationUrlPrefix(String url); - - String getAccessToken(); - - void setAccessToken(String accessToken); - - void setTransactionId(String transactionId); - - String getTransactionId(); - - // request purchased items are not consumed - void requestPurchased(); - - // callback for requestPurchased() - void callbackPurchased(String receipt, String signature, String sku); - - void callbackDisconnected(); - - void callbackConnected(); - - // true if connected, false otherwise - boolean isConnected(); - - // consume item automatically after purchase. default is true. - void setAutoConsume(boolean autoConsume); - - // consume a specific item - void consume(String sku); - - // query in app item detail info - void querySkuDetails(String[] list); - - void addSkuDetail(String itemJson); - - void completeSkuDetail(); - - void errorSkuDetail(String errorMessage); -} diff --git a/platform/android/java/lib/aidl/com/android/vending/billing/IInAppBillingService.aidl b/platform/android/java/plugins/godotpayment/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl index 0f2bcae338..0f2bcae338 100644 --- a/platform/android/java/lib/aidl/com/android/vending/billing/IInAppBillingService.aidl +++ b/platform/android/java/plugins/godotpayment/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/ConsumeTask.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ConsumeTask.java index 95cc48f536..c15bc232ce 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/ConsumeTask.java +++ b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ConsumeTask.java @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot.payments; +package org.godotengine.godot.plugin.payment; import android.content.Context; import android.os.AsyncTask; diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java index 4a6b611c4d..c7d0a5de65 100644 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java +++ b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java @@ -30,6 +30,7 @@ package org.godotengine.godot.plugin.payment; +import android.content.Intent; import android.support.annotation.NonNull; import android.util.Log; import java.util.ArrayList; @@ -38,28 +39,40 @@ import java.util.List; import org.godotengine.godot.Dictionary; import org.godotengine.godot.Godot; import org.godotengine.godot.GodotLib; -import org.godotengine.godot.payments.GodotPaymentInterface; -import org.godotengine.godot.payments.PaymentsManager; import org.godotengine.godot.plugin.GodotPlugin; import org.json.JSONException; import org.json.JSONObject; -public class GodotPayment extends GodotPlugin implements GodotPaymentInterface { +public class GodotPayment extends GodotPlugin { private Integer purchaseCallbackId = 0; private String accessToken; private String purchaseValidationUrlPrefix; private String transactionId; - private PaymentsManager mPaymentManager; - private Dictionary mSkuDetails = new Dictionary(); + private final PaymentsManager mPaymentManager; + private final Dictionary mSkuDetails = new Dictionary(); public GodotPayment(Godot godot) { super(godot); - mPaymentManager = godot.getPaymentsManager(); - mPaymentManager.setBaseSingleton(this); + mPaymentManager = new PaymentsManager(godot, this); + mPaymentManager.initService(); } @Override + public void onMainActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == PaymentsManager.REQUEST_CODE_FOR_PURCHASE) { + mPaymentManager.processPurchaseResponse(resultCode, data); + } + } + + @Override + public void onMainDestroy() { + super.onMainDestroy(); + if (mPaymentManager != null) { + mPaymentManager.destroy(); + } + } + public void purchase(final String sku, final String transactionId) { runOnUiThread(new Runnable() { @Override @@ -69,7 +82,6 @@ public class GodotPayment extends GodotPlugin implements GodotPaymentInterface { }); } - @Override public void consumeUnconsumedPurchases() { runOnUiThread(new Runnable() { @Override @@ -81,89 +93,72 @@ public class GodotPayment extends GodotPlugin implements GodotPaymentInterface { private String signature; - @Override public String getSignature() { return this.signature; } - @Override public void callbackSuccess(String ticket, String signature, String sku) { GodotLib.calldeferred(purchaseCallbackId, "purchase_success", new Object[] { ticket, signature, sku }); } - @Override public void callbackSuccessProductMassConsumed(String ticket, String signature, String sku) { Log.d(this.getClass().getName(), "callbackSuccessProductMassConsumed > " + ticket + "," + signature + "," + sku); GodotLib.calldeferred(purchaseCallbackId, "consume_success", new Object[] { ticket, signature, sku }); } - @Override public void callbackSuccessNoUnconsumedPurchases() { GodotLib.calldeferred(purchaseCallbackId, "consume_not_required", new Object[] {}); } - @Override public void callbackFailConsume(String message) { GodotLib.calldeferred(purchaseCallbackId, "consume_fail", new Object[] { message }); } - @Override public void callbackFail(String message) { GodotLib.calldeferred(purchaseCallbackId, "purchase_fail", new Object[] { message }); } - @Override public void callbackCancel() { GodotLib.calldeferred(purchaseCallbackId, "purchase_cancel", new Object[] {}); } - @Override public void callbackAlreadyOwned(String sku) { GodotLib.calldeferred(purchaseCallbackId, "purchase_owned", new Object[] { sku }); } - @Override public int getPurchaseCallbackId() { return purchaseCallbackId; } - @Override public void setPurchaseCallbackId(int purchaseCallbackId) { this.purchaseCallbackId = purchaseCallbackId; } - @Override public String getPurchaseValidationUrlPrefix() { return this.purchaseValidationUrlPrefix; } - @Override public void setPurchaseValidationUrlPrefix(String url) { this.purchaseValidationUrlPrefix = url; } - @Override public String getAccessToken() { return accessToken; } - @Override public void setAccessToken(String accessToken) { this.accessToken = accessToken; } - @Override public void setTransactionId(String transactionId) { this.transactionId = transactionId; } - @Override public String getTransactionId() { return this.transactionId; } // request purchased items are not consumed - @Override public void requestPurchased() { runOnUiThread(new Runnable() { @Override @@ -174,41 +169,34 @@ public class GodotPayment extends GodotPlugin implements GodotPaymentInterface { } // callback for requestPurchased() - @Override public void callbackPurchased(String receipt, String signature, String sku) { GodotLib.calldeferred(purchaseCallbackId, "has_purchased", new Object[] { receipt, signature, sku }); } - @Override public void callbackDisconnected() { GodotLib.calldeferred(purchaseCallbackId, "iap_disconnected", new Object[] {}); } - @Override public void callbackConnected() { GodotLib.calldeferred(purchaseCallbackId, "iap_connected", new Object[] {}); } // true if connected, false otherwise - @Override public boolean isConnected() { return mPaymentManager.isConnected(); } // consume item automatically after purchase. default is true. - @Override public void setAutoConsume(boolean autoConsume) { mPaymentManager.setAutoConsume(autoConsume); } // consume a specific item - @Override public void consume(String sku) { mPaymentManager.consume(sku); } // query in app item detail info - @Override public void querySkuDetails(String[] list) { List<String> nKeys = Arrays.asList(list); List<String> cKeys = Arrays.asList(mSkuDetails.get_keys()); @@ -225,7 +213,6 @@ public class GodotPayment extends GodotPlugin implements GodotPaymentInterface { } } - @Override public void addSkuDetail(String itemJson) { JSONObject o = null; try { @@ -244,12 +231,10 @@ public class GodotPayment extends GodotPlugin implements GodotPaymentInterface { } } - @Override public void completeSkuDetail() { GodotLib.calldeferred(purchaseCallbackId, "sku_details_complete", new Object[] { mSkuDetails }); } - @Override public void errorSkuDetail(String errorMessage) { GodotLib.calldeferred(purchaseCallbackId, "sku_details_error", new Object[] { errorMessage }); } @@ -263,6 +248,8 @@ public class GodotPayment extends GodotPlugin implements GodotPaymentInterface { @NonNull @Override public List<String> getPluginMethods() { - return Arrays.asList("purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix", "setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased", "setAutoConsume", "consume", "querySkuDetails", "isConnected"); + return Arrays.asList("purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix", + "setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased", + "setAutoConsume", "consume", "querySkuDetails", "isConnected"); } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/HandlePurchaseTask.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/HandlePurchaseTask.java index 23d693cc8c..fe5685288b 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/HandlePurchaseTask.java +++ b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/HandlePurchaseTask.java @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot.payments; +package org.godotengine.godot.plugin.payment; import android.app.Activity; import android.content.Intent; diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsCache.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsCache.java index 84a7eda6e0..d5919e3d9d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsCache.java +++ b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsCache.java @@ -28,11 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot.payments; +package org.godotengine.godot.plugin.payment; import android.content.Context; import android.content.SharedPreferences; -import android.util.Log; public class PaymentsCache { diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsManager.java index 9bf6650f84..bded1f452f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/PaymentsManager.java +++ b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsManager.java @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot.payments; +package org.godotengine.godot.plugin.payment; import android.app.Activity; import android.content.ComponentName; @@ -52,20 +52,13 @@ public class PaymentsManager { public static final int REQUEST_CODE_FOR_PURCHASE = 0x1001; private static boolean auto_consume = true; - private Activity activity; + private final Activity activity; + private final GodotPayment godotPayment; IInAppBillingService mService; - public void setActivity(Activity activity) { - this.activity = activity; - } - - public static PaymentsManager createManager(Activity activity) { - PaymentsManager manager = new PaymentsManager(activity); - return manager; - } - - private PaymentsManager(Activity activity) { + PaymentsManager(Activity activity, GodotPayment godotPayment) { this.activity = activity; + this.godotPayment = godotPayment; } public PaymentsManager initService() { @@ -409,10 +402,4 @@ public class PaymentsManager { })) .start(); } - - private GodotPaymentInterface godotPayment; - - public void setBaseSingleton(GodotPaymentInterface godotPaymentInterface) { - this.godotPayment = godotPaymentInterface; - } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/PurchaseTask.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PurchaseTask.java index 09c9349124..eecd1d2151 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/PurchaseTask.java +++ b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PurchaseTask.java @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot.payments; +package org.godotengine.godot.plugin.payment; import android.app.Activity; import android.app.PendingIntent; diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ReleaseAllConsumablesTask.java index a101780511..b7bd638feb 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java +++ b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ReleaseAllConsumablesTask.java @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot.payments; +package org.godotengine.godot.plugin.payment; import android.content.Context; import android.os.AsyncTask; diff --git a/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ValidateTask.java index 10c314aecf..179cc08ed1 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/payments/ValidateTask.java +++ b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ValidateTask.java @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -package org.godotengine.godot.payments; +package org.godotengine.godot.plugin.payment; import android.app.Activity; import android.app.ProgressDialog; @@ -42,7 +42,7 @@ import org.json.JSONObject; abstract public class ValidateTask { private Activity context; - private GodotPaymentInterface godotPayments; + private GodotPayment godotPayments; private ProgressDialog dialog; private String mSku; @@ -79,7 +79,7 @@ abstract public class ValidateTask { } } - public ValidateTask(Activity context, GodotPaymentInterface godotPayments) { + public ValidateTask(Activity context, GodotPayment godotPayments) { this.context = context; this.godotPayments = godotPayments; } diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index 9e9b17fb99..13550c4b11 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -34,7 +34,7 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error, Variant &ret) { - Map<StringName, List<MethodInfo> >::Element *M = methods.find(p_method); + Map<StringName, List<MethodInfo>>::Element *M = methods.find(p_method); if (!M) return false; diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 7e2b0d948e..8021d6dd07 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -91,7 +91,7 @@ void OS_Android::initialize_core() { FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES); else { #ifdef USE_JAVA_FILE_ACCESS - FileAccess::make_default<FileAccessBufferedFA<FileAccessJAndroid> >(FileAccess::ACCESS_RESOURCES); + FileAccess::make_default<FileAccessBufferedFA<FileAccessJAndroid>>(FileAccess::ACCESS_RESOURCES); #else //FileAccess::make_default<FileAccessBufferedFA<FileAccessAndroid> >(FileAccess::ACCESS_RESOURCES); FileAccess::make_default<FileAccessAndroid>(FileAccess::ACCESS_RESOURCES); diff --git a/platform/iphone/app_delegate.h b/platform/iphone/app_delegate.h index 6b3b7ad5bc..27552d781a 100644 --- a/platform/iphone/app_delegate.h +++ b/platform/iphone/app_delegate.h @@ -36,9 +36,11 @@ #import <CoreMotion/CoreMotion.h> -#if defined(OPENGL_ENABLED) -@interface AppDelegate : NSObject <UIApplicationDelegate, GLViewDelegate> { -#endif +// FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented again, +// so it can't be done with compilation time branching. +//#if defined(OPENGL_ENABLED) +//@interface AppDelegate : NSObject <UIApplicationDelegate, GLViewDelegate> { +//#endif #if defined(VULKAN_ENABLED) @interface AppDelegate : NSObject <UIApplicationDelegate> { #endif diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm index acc3e5d4e0..0ac8bb7a56 100644 --- a/platform/iphone/app_delegate.mm +++ b/platform/iphone/app_delegate.mm @@ -648,7 +648,6 @@ static int frame_count = 0; view_controller = [[ViewController alloc] init]; view_controller.view = glView; - _set_keep_screen_on(bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)) ? YES : NO); glView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO; diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index 7cef2351e3..08f3c3f91f 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -410,7 +410,7 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ } String EditorExportPlatformIOS::_get_additional_plist_content() { - Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins(); + Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); String result; for (int i = 0; i < export_plugins.size(); ++i) { result += export_plugins[i]->get_ios_plist_content(); @@ -419,7 +419,7 @@ String EditorExportPlatformIOS::_get_additional_plist_content() { } String EditorExportPlatformIOS::_get_linker_flags() { - Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins(); + Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); String result; for (int i = 0; i < export_plugins.size(); ++i) { String flags = export_plugins[i]->get_ios_linker_flags(); @@ -434,7 +434,7 @@ String EditorExportPlatformIOS::_get_linker_flags() { } String EditorExportPlatformIOS::_get_cpp_code() { - Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins(); + Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); String result; for (int i = 0; i < export_plugins.size(); ++i) { result += export_plugins[i]->get_ios_cpp_code(); @@ -776,7 +776,7 @@ struct ExportLibsData { }; void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets) { - Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins(); + Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); Vector<String> frameworks; for (int i = 0; i < export_plugins.size(); ++i) { Vector<String> plugin_frameworks = export_plugins[i]->get_ios_frameworks(); @@ -920,7 +920,7 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir } Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets) { - Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins(); + Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); for (int i = 0; i < export_plugins.size(); i++) { Vector<String> frameworks = export_plugins[i]->get_ios_frameworks(); Error err = _export_additional_assets(p_out_dir, frameworks, true, r_exported_assets); diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index 85a633442e..d3cd8f76b7 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -10,8 +10,11 @@ javascript_files = [ 'os_javascript.cpp', ] -build = env.add_program(['#bin/godot${PROGSUFFIX}.js', '#bin/godot${PROGSUFFIX}.wasm'], javascript_files); -js, wasm = build +build_targets = ['#bin/godot${PROGSUFFIX}.js', '#bin/godot${PROGSUFFIX}.wasm'] +if env['threads_enabled']: + build_targets.append('#bin/godot${PROGSUFFIX}.worker.js') + +build = env.add_program(build_targets, javascript_files) js_libraries = [ 'http_request.js', @@ -27,18 +30,38 @@ for module in js_modules: env.Append(LINKFLAGS=['--pre-js', env.File(module).path]) env.Depends(build, js_modules) -wrapper_start = env.File('pre.js') -wrapper_end = env.File('engine.js') -js_wrapped = env.Textfile('#bin/godot', [wrapper_start, js, wrapper_end], TEXTFILESUFFIX='${PROGSUFFIX}.wrapped.js') +engine = [ + 'engine/preloader.js', + 'engine/loader.js', + 'engine/utils.js', + 'engine/engine.js', +] +externs = [ + env.File('#platform/javascript/engine/externs.js') +] +js_engine = env.CreateEngineFile('#bin/godot${PROGSUFFIX}.engine.js', engine, externs) +env.Depends(js_engine, externs) + +wrap_list = [ + build[0], + js_engine, +] +js_wrapped = env.Textfile('#bin/godot', [env.File(f) for f in wrap_list], TEXTFILESUFFIX='${PROGSUFFIX}.wrapped.js') zip_dir = env.Dir('#bin/.javascript_zip') -zip_files = env.InstallAs([ +out_files = [ zip_dir.File('godot.js'), zip_dir.File('godot.wasm'), zip_dir.File('godot.html') -], [ +] +in_files = [ js_wrapped, - wasm, + build[1], '#misc/dist/html/full-size.html' -]) +] +if env['threads_enabled']: + in_files.append(build[2]) + out_files.append(zip_dir.File('godot.worker.js')) + +zip_files = env.InstallAs(out_files, in_files) env.Zip('#bin/godot', zip_files, ZIPROOT=zip_dir, ZIPSUFFIX='${PROGSUFFIX}${ZIPSUFFIX}', ZIPCOMSTR='Archving $SOURCES as $TARGET') diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp index f1bc7c4382..d63c6a40a5 100644 --- a/platform/javascript/audio_driver_javascript.cpp +++ b/platform/javascript/audio_driver_javascript.cpp @@ -69,31 +69,37 @@ void AudioDriverJavaScript::process_capture(float sample) { Error AudioDriverJavaScript::init() { /* clang-format off */ - EM_ASM({ - _audioDriver_audioContext = new (window.AudioContext || window.webkitAudioContext); - _audioDriver_audioInput = null; - _audioDriver_inputStream = null; - _audioDriver_scriptNode = null; + _driver_id = EM_ASM_INT({ + return Module.IDHandler.add({ + 'context': new (window.AudioContext || window.webkitAudioContext), + 'input': null, + 'stream': null, + 'script': null + }); }); /* clang-format on */ int channel_count = get_total_channels_by_speaker_mode(get_speaker_mode()); /* clang-format off */ buffer_length = EM_ASM_INT({ - var CHANNEL_COUNT = $0; + var ref = Module.IDHandler.get($0); + var ctx = ref['context']; + var CHANNEL_COUNT = $1; - var channelCount = _audioDriver_audioContext.destination.channelCount; + var channelCount = ctx.destination.channelCount; + var script = null; try { // Try letting the browser recommend a buffer length. - _audioDriver_scriptNode = _audioDriver_audioContext.createScriptProcessor(0, 2, channelCount); + script = ctx.createScriptProcessor(0, 2, channelCount); } catch (e) { // ...otherwise, default to 4096. - _audioDriver_scriptNode = _audioDriver_audioContext.createScriptProcessor(4096, 2, channelCount); + script = ctx.createScriptProcessor(4096, 2, channelCount); } - _audioDriver_scriptNode.connect(_audioDriver_audioContext.destination); + script.connect(ctx.destination); + ref['script'] = script; - return _audioDriver_scriptNode.bufferSize; - }, channel_count); + return script.bufferSize; + }, _driver_id, channel_count); /* clang-format on */ if (!buffer_length) { return FAILED; @@ -112,11 +118,12 @@ void AudioDriverJavaScript::start() { /* clang-format off */ EM_ASM({ - var INTERNAL_BUFFER_PTR = $0; + const ref = Module.IDHandler.get($0); + var INTERNAL_BUFFER_PTR = $1; var audioDriverMixFunction = cwrap('audio_driver_js_mix'); var audioDriverProcessCapture = cwrap('audio_driver_process_capture', null, ['number']); - _audioDriver_scriptNode.onaudioprocess = function(audioProcessingEvent) { + ref['script'].onaudioprocess = function(audioProcessingEvent) { audioDriverMixFunction(); var input = audioProcessingEvent.inputBuffer; @@ -133,7 +140,7 @@ void AudioDriverJavaScript::start() { } } - if (_audioDriver_audioInput) { + if (ref['input']) { var inputDataL = input.getChannelData(0); var inputDataR = input.getChannelData(1); for (var i = 0; i < inputDataL.length; i++) { @@ -142,34 +149,37 @@ void AudioDriverJavaScript::start() { } } }; - }, internal_buffer); + }, _driver_id, internal_buffer); /* clang-format on */ } void AudioDriverJavaScript::resume() { /* clang-format off */ EM_ASM({ - if (_audioDriver_audioContext.resume) - _audioDriver_audioContext.resume(); - }); + const ref = Module.IDHandler.get($0); + if (ref && ref['context'] && ref['context'].resume) + ref['context'].resume(); + }, _driver_id); /* clang-format on */ } int AudioDriverJavaScript::get_mix_rate() const { /* clang-format off */ - return EM_ASM_INT_V({ - return _audioDriver_audioContext.sampleRate; - }); + return EM_ASM_INT({ + const ref = Module.IDHandler.get($0); + return ref && ref['context'] ? ref['context'].sampleRate : 0; + }, _driver_id); /* clang-format on */ } AudioDriver::SpeakerMode AudioDriverJavaScript::get_speaker_mode() const { /* clang-format off */ - return get_speaker_mode_by_total_channels(EM_ASM_INT_V({ - return _audioDriver_audioContext.destination.channelCount; - })); + return get_speaker_mode_by_total_channels(EM_ASM_INT({ + const ref = Module.IDHandler.get($0); + return ref && ref['context'] ? ref['context'].destination.channelCount : 0; + }, _driver_id)); /* clang-format on */ } @@ -184,16 +194,15 @@ void AudioDriverJavaScript::finish() { /* clang-format off */ EM_ASM({ - _audioDriver_audioContext = null; - _audioDriver_audioInput = null; - _audioDriver_scriptNode = null; - }); + Module.IDHandler.remove($0); + }, _driver_id); /* clang-format on */ if (internal_buffer) { memdelete_arr(internal_buffer); internal_buffer = NULL; } + _driver_id = 0; } Error AudioDriverJavaScript::capture_start() { @@ -203,9 +212,10 @@ Error AudioDriverJavaScript::capture_start() { /* clang-format off */ EM_ASM({ function gotMediaInput(stream) { - _audioDriver_inputStream = stream; - _audioDriver_audioInput = _audioDriver_audioContext.createMediaStreamSource(stream); - _audioDriver_audioInput.connect(_audioDriver_scriptNode); + var ref = Module.IDHandler.get($0); + ref['stream'] = stream; + ref['input'] = ref['context'].createMediaStreamSource(stream); + ref['input'].connect(ref['script']); } function gotMediaInputError(e) { @@ -219,7 +229,7 @@ Error AudioDriverJavaScript::capture_start() { navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia; navigator.getUserMedia({"audio": true}, gotMediaInput, gotMediaInputError); } - }); + }, _driver_id); /* clang-format on */ return OK; @@ -229,20 +239,21 @@ Error AudioDriverJavaScript::capture_stop() { /* clang-format off */ EM_ASM({ - if (_audioDriver_inputStream) { - const tracks = _audioDriver_inputStream.getTracks(); + var ref = Module.IDHandler.get($0); + if (ref['stream']) { + const tracks = ref['stream'].getTracks(); for (var i = 0; i < tracks.length; i++) { tracks[i].stop(); } - _audioDriver_inputStream = null; + ref['stream'] = null; } - if (_audioDriver_audioInput) { - _audioDriver_audioInput.disconnect(); - _audioDriver_audioInput = null; + if (ref['input']) { + ref['input'].disconnect(); + ref['input'] = null; } - }); + }, _driver_id); /* clang-format on */ input_buffer.clear(); @@ -252,7 +263,9 @@ Error AudioDriverJavaScript::capture_stop() { AudioDriverJavaScript::AudioDriverJavaScript() { + _driver_id = 0; internal_buffer = NULL; + buffer_length = 0; singleton = this; } diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h index 2bb97ba192..f6f2dacd4e 100644 --- a/platform/javascript/audio_driver_javascript.h +++ b/platform/javascript/audio_driver_javascript.h @@ -37,6 +37,7 @@ class AudioDriverJavaScript : public AudioDriver { float *internal_buffer; + int _driver_id; int buffer_length; public: diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index 1766833364..fb02752aa7 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -1,5 +1,6 @@ import os +from emscripten_helpers import parse_config, run_closure_compiler, create_engine_file def is_active(): return True @@ -18,6 +19,8 @@ def get_opts(): return [ # eval() can be a security concern, so it can be disabled. BoolVariable('javascript_eval', 'Enable JavaScript eval interface', True), + BoolVariable('threads_enabled', 'Enable WebAssembly Threads support (limited browser support)', False), + BoolVariable('use_closure_compiler', 'Use closure compiler to minimize Javascript code', False), ] @@ -37,7 +40,7 @@ def configure(env): ## Build type - if env['target'] != 'debug': + if env['target'] == 'release': # Use -Os to prioritize optimizing for reduced file size. This is # particularly valuable for the web platform because it directly # decreases download time. @@ -46,38 +49,55 @@ def configure(env): # run-time performance. env.Append(CCFLAGS=['-Os']) env.Append(LINKFLAGS=['-Os']) - if env['target'] == 'release_debug': - env.Append(CPPDEFINES=['DEBUG_ENABLED']) - # Retain function names for backtraces at the cost of file size. - env.Append(LINKFLAGS=['--profiling-funcs']) - else: + elif env['target'] == 'release_debug': + env.Append(CCFLAGS=['-Os']) + env.Append(LINKFLAGS=['-Os']) + env.Append(CPPDEFINES=['DEBUG_ENABLED']) + # Retain function names for backtraces at the cost of file size. + env.Append(LINKFLAGS=['--profiling-funcs']) + else: # 'debug' env.Append(CPPDEFINES=['DEBUG_ENABLED']) env.Append(CCFLAGS=['-O1', '-g']) env.Append(LINKFLAGS=['-O1', '-g']) env.Append(LINKFLAGS=['-s', 'ASSERTIONS=1']) - ## Compiler configuration + if env['tools']: + if not env['threads_enabled']: + raise RuntimeError("Threads must be enabled to build the editor. Please add the 'threads_enabled=yes' option") + # Tools need more memory. Initial stack memory in bytes. See `src/settings.js` in emscripten repository (will be renamed to INITIAL_MEMORY). + env.Append(LINKFLAGS=['-s', 'TOTAL_MEMORY=33554432']) + else: + # Disable exceptions and rtti on non-tools (template) builds + # These flags help keep the file size down. + env.Append(CCFLAGS=['-fno-exceptions', '-fno-rtti']) + # Don't use dynamic_cast, necessary with no-rtti. + env.Append(CPPDEFINES=['NO_SAFE_CAST']) + ## Copy env variables. env['ENV'] = os.environ - em_config_file = os.getenv('EM_CONFIG') or os.path.expanduser('~/.emscripten') - if not os.path.exists(em_config_file): - raise RuntimeError("Emscripten configuration file '%s' does not exist" % em_config_file) - with open(em_config_file) as f: - em_config = {} - try: - # Emscripten configuration file is a Python file with simple assignments. - exec(f.read(), em_config) - except StandardError as e: - raise RuntimeError("Emscripten configuration file '%s' is invalid:\n%s" % (em_config_file, e)) - if 'BINARYEN_ROOT' in em_config and os.path.isdir(os.path.join(em_config.get('BINARYEN_ROOT'), 'emscripten')): - # New style, emscripten path as a subfolder of BINARYEN_ROOT - env.PrependENVPath('PATH', os.path.join(em_config.get('BINARYEN_ROOT'), 'emscripten')) - elif 'EMSCRIPTEN_ROOT' in em_config: - # Old style (but can be there as a result from previous activation, so do last) - env.PrependENVPath('PATH', em_config.get('EMSCRIPTEN_ROOT')) - else: - raise RuntimeError("'BINARYEN_ROOT' or 'EMSCRIPTEN_ROOT' missing in Emscripten configuration file '%s'" % em_config_file) + # LTO + if env['use_lto']: + env.Append(CCFLAGS=['-s', 'WASM_OBJECT_FILES=0']) + env.Append(LINKFLAGS=['-s', 'WASM_OBJECT_FILES=0']) + env.Append(LINKFLAGS=['--llvm-lto', '1']) + + # Closure compiler + if env['use_closure_compiler']: + # For emscripten support code. + env.Append(LINKFLAGS=['--closure', '1']) + # Register builder for our Engine files + jscc = env.Builder(generator=run_closure_compiler, suffix='.cc.js', src_suffix='.js') + env.Append(BUILDERS = {'BuildJS' : jscc}) + + # Add method that joins/compiles our Engine files. + env.AddMethod(create_engine_file, "CreateEngineFile") + + # Closure compiler extern and support for ecmascript specs (const, let, etc). + env['ENV']['EMCC_CLOSURE_ARGS'] = '--language_in ECMASCRIPT6' + + em_config = parse_config() + env.PrependENVPath('PATH', em_config['EMCC_ROOT']) env['CC'] = 'emcc' env['CXX'] = 'em++' @@ -104,44 +124,31 @@ def configure(env): env['LIBPREFIXES'] = ['$LIBPREFIX'] env['LIBSUFFIXES'] = ['$LIBSUFFIX'] - ## Compile flags - env.Prepend(CPPPATH=['#platform/javascript']) env.Append(CPPDEFINES=['JAVASCRIPT_ENABLED', 'UNIX_ENABLED']) - # No multi-threading (SharedArrayBuffer) available yet, - # once feasible also consider memory buffer size issues. - env.Append(CPPDEFINES=['NO_THREADS']) - - # Disable exceptions and rtti on non-tools (template) builds - if not env['tools']: - # These flags help keep the file size down. - env.Append(CCFLAGS=['-fno-exceptions', '-fno-rtti']) - # Don't use dynamic_cast, necessary with no-rtti. - env.Append(CPPDEFINES=['NO_SAFE_CAST']) - if env['javascript_eval']: env.Append(CPPDEFINES=['JAVASCRIPT_EVAL_ENABLED']) - ## Link flags + # Thread support (via SharedArrayBuffer). + if env['threads_enabled']: + env.Append(CPPDEFINES=['PTHREAD_NO_RENAME']) + env.Append(CCFLAGS=['-s', 'USE_PTHREADS=1']) + env.Append(LINKFLAGS=['-s', 'USE_PTHREADS=1']) + env.Append(LINKFLAGS=['-s', 'PTHREAD_POOL_SIZE=4']) + env.Append(LINKFLAGS=['-s', 'WASM_MEM_MAX=2048MB']) + else: + env.Append(CPPDEFINES=['NO_THREADS']) + + # Reduce code size by generating less support code (e.g. skip NodeJS support). + env.Append(LINKFLAGS=['-s', 'ENVIRONMENT=web,worker']) # We use IDBFS in javascript_main.cpp. Since Emscripten 1.39.1 it needs to # be linked explicitly. env.Append(LIBS=['idbfs.js']) env.Append(LINKFLAGS=['-s', 'BINARYEN=1']) - - # Only include the JavaScript support code for the web environment - # (i.e. exclude Node.js and other unused environments). - # This makes the JavaScript support code about 4 KB smaller. - env.Append(LINKFLAGS=['-s', 'ENVIRONMENT=web']) - - # This needs to be defined for Emscripten using 'fastcomp' (default pre-1.39.0) - # and undefined if using 'upstream'. And to make things simple, earlier - # Emscripten versions didn't include 'fastcomp' in their path, so we check - # against the presence of 'upstream' to conditionally add the flag. - if not "upstream" in em_config['EMSCRIPTEN_ROOT']: - env.Append(LINKFLAGS=['-s', 'BINARYEN_TRAP_MODE=\'clamp\'']) + env.Append(LINKFLAGS=['-s', 'MODULARIZE=1', '-s', 'EXPORT_NAME="Godot"']) # Allow increasing memory buffer size during runtime. This is efficient # when using WebAssembly (in comparison to asm.js) and works well for @@ -153,8 +160,5 @@ def configure(env): env.Append(LINKFLAGS=['-s', 'INVOKE_RUN=0']) - # TODO: Reevaluate usage of this setting now that engine.js manages engine runtime. - env.Append(LINKFLAGS=['-s', 'NO_EXIT_RUNTIME=1']) - - #adding flag due to issue with emscripten 1.38.41 callMain method https://github.com/emscripten-core/emscripten/blob/incoming/ChangeLog.md#v13841-08072019 - env.Append(LINKFLAGS=['-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["callMain"]']) + # callMain for manual start, FS for preloading. + env.Append(LINKFLAGS=['-s', 'EXTRA_EXPORTED_RUNTIME_METHODS=["callMain", "FS"]']) diff --git a/platform/javascript/emscripten_helpers.py b/platform/javascript/emscripten_helpers.py new file mode 100644 index 0000000000..bda5b40a74 --- /dev/null +++ b/platform/javascript/emscripten_helpers.py @@ -0,0 +1,37 @@ +import os + +def parse_config(): + em_config_file = os.getenv('EM_CONFIG') or os.path.expanduser('~/.emscripten') + if not os.path.exists(em_config_file): + raise RuntimeError("Emscripten configuration file '%s' does not exist" % em_config_file) + + normalized = {} + em_config = {} + with open(em_config_file) as f: + try: + # Emscripten configuration file is a Python file with simple assignments. + exec(f.read(), em_config) + except StandardError as e: + raise RuntimeError("Emscripten configuration file '%s' is invalid:\n%s" % (em_config_file, e)) + normalized['EMCC_ROOT'] = em_config.get('EMSCRIPTEN_ROOT') + normalized['NODE_JS'] = em_config.get('NODE_JS') + normalized['CLOSURE_BIN'] = os.path.join(normalized['EMCC_ROOT'], 'node_modules', '.bin', 'google-closure-compiler') + return normalized + + +def run_closure_compiler(target, source, env, for_signature): + cfg = parse_config() + cmd = [cfg['NODE_JS'], cfg['CLOSURE_BIN']] + cmd.extend(['--compilation_level', 'ADVANCED_OPTIMIZATIONS']) + for f in env['JSEXTERNS']: + cmd.extend(['--externs', f.get_abspath()]) + for f in source: + cmd.extend(['--js', f.get_abspath()]) + cmd.extend(['--js_output_file', target[0].get_abspath()]) + return ' '.join(cmd) + + +def create_engine_file(env, target, source, externs): + if env['use_closure_compiler']: + return env.BuildJS(target, source, JSEXTERNS=externs) + return env.Textfile(target, [env.File(s) for s in source]) diff --git a/platform/javascript/engine.js b/platform/javascript/engine.js deleted file mode 100644 index 227accadb0..0000000000 --- a/platform/javascript/engine.js +++ /dev/null @@ -1,411 +0,0 @@ - // The following is concatenated with generated code, and acts as the end - // of a wrapper for said code. See pre.js for the other part of the - // wrapper. - exposedLibs['PATH'] = PATH; - exposedLibs['FS'] = FS; - return Module; - }, -}; - -(function() { - var engine = Engine; - - var DOWNLOAD_ATTEMPTS_MAX = 4; - - var basePath = null; - var wasmFilenameExtensionOverride = null; - var engineLoadPromise = null; - - var loadingFiles = {}; - - function getPathLeaf(path) { - - while (path.endsWith('/')) - path = path.slice(0, -1); - return path.slice(path.lastIndexOf('/') + 1); - } - - function getBasePath(path) { - - if (path.endsWith('/')) - path = path.slice(0, -1); - if (path.lastIndexOf('.') > path.lastIndexOf('/')) - path = path.slice(0, path.lastIndexOf('.')); - return path; - } - - function getBaseName(path) { - - return getPathLeaf(getBasePath(path)); - } - - Engine = function Engine() { - - this.rtenv = null; - - var LIBS = {}; - - var initPromise = null; - var unloadAfterInit = true; - - var preloadedFiles = []; - - var resizeCanvasOnStart = true; - var progressFunc = null; - var preloadProgressTracker = {}; - var lastProgress = { loaded: 0, total: 0 }; - - var canvas = null; - var executableName = null; - var locale = null; - var stdout = null; - var stderr = null; - - this.init = function(newBasePath) { - - if (!initPromise) { - initPromise = Engine.load(newBasePath).then( - instantiate.bind(this) - ); - requestAnimationFrame(animateProgress); - if (unloadAfterInit) - initPromise.then(Engine.unloadEngine); - } - return initPromise; - }; - - function instantiate(wasmBuf) { - - var rtenvProps = { - engine: this, - ENV: {}, - }; - if (typeof stdout === 'function') - rtenvProps.print = stdout; - if (typeof stderr === 'function') - rtenvProps.printErr = stderr; - rtenvProps.instantiateWasm = function(imports, onSuccess) { - WebAssembly.instantiate(wasmBuf, imports).then(function(result) { - onSuccess(result.instance); - }); - return {}; - }; - - return new Promise(function(resolve, reject) { - rtenvProps.onRuntimeInitialized = resolve; - rtenvProps.onAbort = reject; - rtenvProps.thisProgram = executableName; - rtenvProps.engine.rtenv = Engine.RuntimeEnvironment(rtenvProps, LIBS); - }); - } - - this.preloadFile = function(pathOrBuffer, destPath) { - - if (pathOrBuffer instanceof ArrayBuffer) { - pathOrBuffer = new Uint8Array(pathOrBuffer); - } else if (ArrayBuffer.isView(pathOrBuffer)) { - pathOrBuffer = new Uint8Array(pathOrBuffer.buffer); - } - if (pathOrBuffer instanceof Uint8Array) { - preloadedFiles.push({ - path: destPath, - buffer: pathOrBuffer - }); - return Promise.resolve(); - } else if (typeof pathOrBuffer === 'string') { - return loadPromise(pathOrBuffer, preloadProgressTracker).then(function(xhr) { - preloadedFiles.push({ - path: destPath || pathOrBuffer, - buffer: xhr.response - }); - }); - } else { - throw Promise.reject("Invalid object for preloading"); - } - }; - - this.start = function() { - - return this.init().then( - Function.prototype.apply.bind(synchronousStart, this, arguments) - ); - }; - - this.startGame = function(execName, mainPack) { - - executableName = execName; - var mainArgs = [ '--main-pack', getPathLeaf(mainPack) ]; - - return Promise.all([ - this.init(getBasePath(execName)), - this.preloadFile(mainPack, getPathLeaf(mainPack)) - ]).then( - Function.prototype.apply.bind(synchronousStart, this, mainArgs) - ); - }; - - function synchronousStart() { - - if (canvas instanceof HTMLCanvasElement) { - this.rtenv.canvas = canvas; - } else { - var firstCanvas = document.getElementsByTagName('canvas')[0]; - if (firstCanvas instanceof HTMLCanvasElement) { - this.rtenv.canvas = firstCanvas; - } else { - throw new Error("No canvas found"); - } - } - - var actualCanvas = this.rtenv.canvas; - // canvas can grab focus on click - if (actualCanvas.tabIndex < 0) { - actualCanvas.tabIndex = 0; - } - // necessary to calculate cursor coordinates correctly - actualCanvas.style.padding = 0; - actualCanvas.style.borderWidth = 0; - actualCanvas.style.borderStyle = 'none'; - // disable right-click context menu - actualCanvas.addEventListener('contextmenu', function(ev) { - ev.preventDefault(); - }, false); - // until context restoration is implemented - actualCanvas.addEventListener('webglcontextlost', function(ev) { - alert("WebGL context lost, please reload the page"); - ev.preventDefault(); - }, false); - - if (locale) { - this.rtenv.locale = locale; - } else { - this.rtenv.locale = navigator.languages ? navigator.languages[0] : navigator.language; - } - this.rtenv.locale = this.rtenv.locale.split('.')[0]; - this.rtenv.resizeCanvasOnStart = resizeCanvasOnStart; - - preloadedFiles.forEach(function(file) { - var dir = LIBS.PATH.dirname(file.path); - try { - LIBS.FS.stat(dir); - } catch (e) { - if (e.code !== 'ENOENT') { - throw e; - } - LIBS.FS.mkdirTree(dir); - } - // With memory growth, canOwn should be false. - LIBS.FS.createDataFile(file.path, null, new Uint8Array(file.buffer), true, true, false); - }, this); - - preloadedFiles = null; - initPromise = null; - this.rtenv.callMain(arguments); - } - - this.setProgressFunc = function(func) { - progressFunc = func; - }; - - this.setResizeCanvasOnStart = function(enabled) { - resizeCanvasOnStart = enabled; - }; - - function animateProgress() { - - var loaded = 0; - var total = 0; - var totalIsValid = true; - var progressIsFinal = true; - - [loadingFiles, preloadProgressTracker].forEach(function(tracker) { - Object.keys(tracker).forEach(function(file) { - if (!tracker[file].final) - progressIsFinal = false; - if (!totalIsValid || tracker[file].total === 0) { - totalIsValid = false; - total = 0; - } else { - total += tracker[file].total; - } - loaded += tracker[file].loaded; - }); - }); - if (loaded !== lastProgress.loaded || total !== lastProgress.total) { - lastProgress.loaded = loaded; - lastProgress.total = total; - if (typeof progressFunc === 'function') - progressFunc(loaded, total); - } - if (!progressIsFinal) - requestAnimationFrame(animateProgress); - } - - this.setCanvas = function(elem) { - canvas = elem; - }; - - this.setExecutableName = function(newName) { - - executableName = newName; - }; - - this.setLocale = function(newLocale) { - - locale = newLocale; - }; - - this.setUnloadAfterInit = function(enabled) { - - if (enabled && !unloadAfterInit && initPromise) { - initPromise.then(Engine.unloadEngine); - } - unloadAfterInit = enabled; - }; - - this.setStdoutFunc = function(func) { - - var print = function(text) { - if (arguments.length > 1) { - text = Array.prototype.slice.call(arguments).join(" "); - } - func(text); - }; - if (this.rtenv) - this.rtenv.print = print; - stdout = print; - }; - - this.setStderrFunc = function(func) { - - var printErr = function(text) { - if (arguments.length > 1) - text = Array.prototype.slice.call(arguments).join(" "); - func(text); - }; - if (this.rtenv) - this.rtenv.printErr = printErr; - stderr = printErr; - }; - - - }; // Engine() - - Engine.RuntimeEnvironment = engine.RuntimeEnvironment; - - Engine.isWebGLAvailable = function(majorVersion = 1) { - - var testContext = false; - try { - var testCanvas = document.createElement('canvas'); - if (majorVersion === 1) { - testContext = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl'); - } else if (majorVersion === 2) { - testContext = testCanvas.getContext('webgl2') || testCanvas.getContext('experimental-webgl2'); - } - } catch (e) {} - return !!testContext; - }; - - Engine.setWebAssemblyFilenameExtension = function(override) { - - if (String(override).length === 0) { - throw new Error('Invalid WebAssembly filename extension override'); - } - wasmFilenameExtensionOverride = String(override); - } - - Engine.load = function(newBasePath) { - - if (newBasePath !== undefined) basePath = getBasePath(newBasePath); - if (engineLoadPromise === null) { - if (typeof WebAssembly !== 'object') - return Promise.reject(new Error("Browser doesn't support WebAssembly")); - // TODO cache/retrieve module to/from idb - engineLoadPromise = loadPromise(basePath + '.' + (wasmFilenameExtensionOverride || 'wasm')).then(function(xhr) { - return xhr.response; - }); - engineLoadPromise = engineLoadPromise.catch(function(err) { - engineLoadPromise = null; - throw err; - }); - } - return engineLoadPromise; - }; - - Engine.unload = function() { - engineLoadPromise = null; - }; - - function loadPromise(file, tracker) { - if (tracker === undefined) - tracker = loadingFiles; - return new Promise(function(resolve, reject) { - loadXHR(resolve, reject, file, tracker); - }); - } - - function loadXHR(resolve, reject, file, tracker) { - - var xhr = new XMLHttpRequest; - xhr.open('GET', file); - if (!file.endsWith('.js')) { - xhr.responseType = 'arraybuffer'; - } - ['loadstart', 'progress', 'load', 'error', 'abort'].forEach(function(ev) { - xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker)); - }); - xhr.send(); - } - - function onXHREvent(resolve, reject, file, tracker, ev) { - - if (this.status >= 400) { - - if (this.status < 500 || ++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) { - reject(new Error("Failed loading file '" + file + "': " + this.statusText)); - this.abort(); - return; - } else { - setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000); - } - } - - switch (ev.type) { - case 'loadstart': - if (tracker[file] === undefined) { - tracker[file] = { - total: ev.total, - loaded: ev.loaded, - attempts: 0, - final: false, - }; - } - break; - - case 'progress': - tracker[file].loaded = ev.loaded; - tracker[file].total = ev.total; - break; - - case 'load': - tracker[file].final = true; - resolve(this); - break; - - case 'error': - if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) { - tracker[file].final = true; - reject(new Error("Failed loading file '" + file + "'")); - } else { - setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000); - } - break; - - case 'abort': - tracker[file].final = true; - reject(new Error("Loading file '" + file + "' was aborted.")); - break; - } - } -})(); diff --git a/platform/javascript/engine/engine.js b/platform/javascript/engine/engine.js new file mode 100644 index 0000000000..6d7509377f --- /dev/null +++ b/platform/javascript/engine/engine.js @@ -0,0 +1,184 @@ +Function('return this')()['Engine'] = (function() { + + var unloadAfterInit = true; + var canvas = null; + var resizeCanvasOnStart = false; + var customLocale = 'en_US'; + var wasmExt = '.wasm'; + + var preloader = new Preloader(); + var loader = new Loader(); + var rtenv = null; + + var executableName = ''; + var loadPath = ''; + var loadPromise = null; + var initPromise = null; + var stderr = null; + var stdout = null; + var progressFunc = null; + + function load(basePath) { + if (loadPromise == null) { + loadPath = basePath; + loadPromise = preloader.loadPromise(basePath + wasmExt); + preloader.setProgressFunc(progressFunc); + requestAnimationFrame(preloader.animateProgress); + } + return loadPromise; + }; + + function unload() { + loadPromise = null; + }; + + /** @constructor */ + function Engine() {}; + + Engine.prototype.init = /** @param {string=} basePath */ function(basePath) { + if (initPromise) { + return initPromise; + } + if (!loadPromise) { + if (!basePath) { + initPromise = Promise.reject(new Error("A base path must be provided when calling `init` and the engine is not loaded.")); + return initPromise; + } + load(basePath); + } + var config = {} + if (typeof stdout === 'function') + config.print = stdout; + if (typeof stderr === 'function') + config.printErr = stderr; + initPromise = loader.init(loadPromise, loadPath, config).then(function() { + return new Promise(function(resolve, reject) { + rtenv = loader.env; + if (unloadAfterInit) { + loadPromise = null; + } + resolve(); + }); + }); + return initPromise; + }; + + /** @type {function(string, string):Object} */ + Engine.prototype.preloadFile = function(file, path) { + return preloader.preload(file, path); + }; + + /** @type {function(...string):Object} */ + Engine.prototype.start = function() { + // Start from arguments. + var args = []; + for (var i = 0; i < arguments.length; i++) { + args.push(arguments[i]); + } + var me = this; + return new Promise(function(resolve, reject) { + return me.init().then(function() { + if (!(canvas instanceof HTMLCanvasElement)) { + canvas = Utils.findCanvas(); + } + rtenv['locale'] = customLocale; + rtenv['canvas'] = canvas; + rtenv['thisProgram'] = executableName; + rtenv['resizeCanvasOnStart'] = resizeCanvasOnStart; + loader.start(preloader.preloadedFiles, args).then(function() { + loader = null; + initPromise = null; + resolve(); + }); + }); + }); + }; + + Engine.prototype.startGame = function(execName, mainPack) { + // Start and init with execName as loadPath if not inited. + executableName = execName; + var me = this; + return Promise.all([ + this.init(execName), + this.preloadFile(mainPack, mainPack) + ]).then(function() { + return me.start('--main-pack', mainPack); + }); + }; + + Engine.prototype.setWebAssemblyFilenameExtension = function(override) { + if (String(override).length === 0) { + throw new Error('Invalid WebAssembly filename extension override'); + } + wasmExt = String(override); + }; + + Engine.prototype.setUnloadAfterInit = function(enabled) { + unloadAfterInit = enabled; + }; + + Engine.prototype.setCanvas = function(canvasElem) { + canvas = canvasElem; + }; + + Engine.prototype.setCanvasResizedOnStart = function(enabled) { + resizeCanvasOnStart = enabled; + }; + + Engine.prototype.setLocale = function(locale) { + customLocale = locale; + }; + + Engine.prototype.setExecutableName = function(newName) { + executableName = newName; + }; + + Engine.prototype.setProgressFunc = function(func) { + progressFunc = func; + } + + Engine.prototype.setStdoutFunc = function(func) { + + var print = function(text) { + if (arguments.length > 1) { + text = Array.prototype.slice.call(arguments).join(" "); + } + func(text); + }; + if (rtenv) + rtenv.print = print; + stdout = print; + }; + + Engine.prototype.setStderrFunc = function(func) { + + var printErr = function(text) { + if (arguments.length > 1) + text = Array.prototype.slice.call(arguments).join(" "); + func(text); + }; + if (rtenv) + rtenv.printErr = printErr; + stderr = printErr; + }; + + // Closure compiler exported engine methods. + /** @export */ + Engine['isWebGLAvailable'] = Utils.isWebGLAvailable; + Engine['load'] = load; + Engine['unload'] = unload; + Engine.prototype['init'] = Engine.prototype.init + Engine.prototype['preloadFile'] = Engine.prototype.preloadFile + Engine.prototype['start'] = Engine.prototype.start + Engine.prototype['startGame'] = Engine.prototype.startGame + Engine.prototype['setWebAssemblyFilenameExtension'] = Engine.prototype.setWebAssemblyFilenameExtension + Engine.prototype['setUnloadAfterInit'] = Engine.prototype.setUnloadAfterInit + Engine.prototype['setCanvas'] = Engine.prototype.setCanvas + Engine.prototype['setCanvasResizedOnStart'] = Engine.prototype.setCanvasResizedOnStart + Engine.prototype['setLocale'] = Engine.prototype.setLocale + Engine.prototype['setExecutableName'] = Engine.prototype.setExecutableName + Engine.prototype['setProgressFunc'] = Engine.prototype.setProgressFunc + Engine.prototype['setStdoutFunc'] = Engine.prototype.setStdoutFunc + Engine.prototype['setStderrFunc'] = Engine.prototype.setStderrFunc + return Engine; +})(); diff --git a/platform/javascript/engine/externs.js b/platform/javascript/engine/externs.js new file mode 100644 index 0000000000..1a94dd15ec --- /dev/null +++ b/platform/javascript/engine/externs.js @@ -0,0 +1,3 @@ +var Godot; +var WebAssembly = {}; +WebAssembly.instantiate = function(buffer, imports) {}; diff --git a/platform/javascript/engine/loader.js b/platform/javascript/engine/loader.js new file mode 100644 index 0000000000..d27fbf612e --- /dev/null +++ b/platform/javascript/engine/loader.js @@ -0,0 +1,33 @@ +var Loader = /** @constructor */ function() { + + this.env = null; + + this.init = function(loadPromise, basePath, config) { + var me = this; + return new Promise(function(resolve, reject) { + var cfg = config || {}; + cfg['locateFile'] = Utils.createLocateRewrite(basePath); + cfg['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise); + loadPromise = null; + Godot(cfg).then(function(module) { + me.env = module; + resolve(); + }); + }); + } + + this.start = function(preloadedFiles, args) { + var me = this; + return new Promise(function(resolve, reject) { + if (!me.env) { + reject(new Error('The engine must be initialized before it can be started')); + } + preloadedFiles.forEach(function(file) { + Utils.copyToFS(me.env['FS'], file.path, file.buffer); + }); + preloadedFiles.length = 0; // Clear memory + me.env['callMain'](args); + resolve(); + }); + } +}; diff --git a/platform/javascript/engine/preloader.js b/platform/javascript/engine/preloader.js new file mode 100644 index 0000000000..17918eae38 --- /dev/null +++ b/platform/javascript/engine/preloader.js @@ -0,0 +1,139 @@ +var Preloader = /** @constructor */ function() { + + var DOWNLOAD_ATTEMPTS_MAX = 4; + var progressFunc = null; + var lastProgress = { loaded: 0, total: 0 }; + + var loadingFiles = {}; + this.preloadedFiles = []; + + function loadXHR(resolve, reject, file, tracker) { + var xhr = new XMLHttpRequest; + xhr.open('GET', file); + if (!file.endsWith('.js')) { + xhr.responseType = 'arraybuffer'; + } + ['loadstart', 'progress', 'load', 'error', 'abort'].forEach(function(ev) { + xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker)); + }); + xhr.send(); + } + + function onXHREvent(resolve, reject, file, tracker, ev) { + + if (this.status >= 400) { + + if (this.status < 500 || ++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) { + reject(new Error("Failed loading file '" + file + "': " + this.statusText)); + this.abort(); + return; + } else { + setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000); + } + } + + switch (ev.type) { + case 'loadstart': + if (tracker[file] === undefined) { + tracker[file] = { + total: ev.total, + loaded: ev.loaded, + attempts: 0, + final: false, + }; + } + break; + + case 'progress': + tracker[file].loaded = ev.loaded; + tracker[file].total = ev.total; + break; + + case 'load': + tracker[file].final = true; + resolve(this); + break; + + case 'error': + if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) { + tracker[file].final = true; + reject(new Error("Failed loading file '" + file + "'")); + } else { + setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000); + } + break; + + case 'abort': + tracker[file].final = true; + reject(new Error("Loading file '" + file + "' was aborted.")); + break; + } + } + + this.loadPromise = function(file) { + return new Promise(function(resolve, reject) { + loadXHR(resolve, reject, file, loadingFiles); + }); + } + + this.preload = function(pathOrBuffer, destPath) { + if (pathOrBuffer instanceof ArrayBuffer) { + pathOrBuffer = new Uint8Array(pathOrBuffer); + } else if (ArrayBuffer.isView(pathOrBuffer)) { + pathOrBuffer = new Uint8Array(pathOrBuffer.buffer); + } + if (pathOrBuffer instanceof Uint8Array) { + this.preloadedFiles.push({ + path: destPath, + buffer: pathOrBuffer + }); + return Promise.resolve(); + } else if (typeof pathOrBuffer === 'string') { + var me = this; + return this.loadPromise(pathOrBuffer).then(function(xhr) { + me.preloadedFiles.push({ + path: destPath || pathOrBuffer, + buffer: xhr.response + }); + return Promise.resolve(); + }); + } else { + throw Promise.reject("Invalid object for preloading"); + } + }; + + var animateProgress = function() { + + var loaded = 0; + var total = 0; + var totalIsValid = true; + var progressIsFinal = true; + + Object.keys(loadingFiles).forEach(function(file) { + const stat = loadingFiles[file]; + if (!stat.final) { + progressIsFinal = false; + } + if (!totalIsValid || stat.total === 0) { + totalIsValid = false; + total = 0; + } else { + total += stat.total; + } + loaded += stat.loaded; + }); + if (loaded !== lastProgress.loaded || total !== lastProgress.total) { + lastProgress.loaded = loaded; + lastProgress.total = total; + if (typeof progressFunc === 'function') + progressFunc(loaded, total); + } + if (!progressIsFinal) + requestAnimationFrame(animateProgress); + } + this.animateProgress = animateProgress; // Also exposed to start it. + + this.setProgressFunc = function(callback) { + progressFunc = callback; + } +}; diff --git a/platform/javascript/engine/utils.js b/platform/javascript/engine/utils.js new file mode 100644 index 0000000000..fdff90a923 --- /dev/null +++ b/platform/javascript/engine/utils.js @@ -0,0 +1,69 @@ +var Utils = { + + createLocateRewrite: function(execName) { + function rw(path) { + if (path.endsWith('.worker.js')) { + return execName + '.worker.js'; + } else if (path.endsWith('.js')) { + return execName + '.js'; + } else if (path.endsWith('.wasm')) { + return execName + '.wasm'; + } + } + return rw; + }, + + createInstantiatePromise: function(wasmLoader) { + function instantiateWasm(imports, onSuccess) { + wasmLoader.then(function(xhr) { + WebAssembly.instantiate(xhr.response, imports).then(function(result) { + onSuccess(result['instance'], result['module']); + }); + }); + wasmLoader = null; + return {}; + }; + + return instantiateWasm; + }, + + copyToFS: function(fs, path, buffer) { + var p = path.lastIndexOf("/"); + var dir = "/"; + if (p > 0) { + dir = path.slice(0, path.lastIndexOf("/")); + } + try { + fs.stat(dir); + } catch (e) { + if (e.errno !== 44) { // 'ENOENT', see https://github.com/emscripten-core/emscripten/blob/master/system/lib/libc/musl/arch/emscripten/bits/errno.h + throw e; + } + fs['mkdirTree'](dir); + } + // With memory growth, canOwn should be false. + fs['writeFile'](path, new Uint8Array(buffer), {'flags': 'wx+'}); + }, + + findCanvas: function() { + var nodes = document.getElementsByTagName('canvas'); + if (nodes.length && nodes[0] instanceof HTMLCanvasElement) { + return nodes[0]; + } + throw new Error("No canvas found"); + }, + + isWebGLAvailable: function(majorVersion = 1) { + + var testContext = false; + try { + var testCanvas = document.createElement('canvas'); + if (majorVersion === 1) { + testContext = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl'); + } else if (majorVersion === 2) { + testContext = testCanvas.getContext('webgl2') || testCanvas.getContext('experimental-webgl2'); + } + } catch (e) {} + return !!testContext; + } +}; diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index f0326d5027..da61425747 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -94,6 +94,9 @@ public: } else if (req[1] == basereq + ".js") { filepath += ".js"; ctype = "application/javascript"; + } else if (req[1] == basereq + ".worker.js") { + filepath += ".worker.js"; + ctype = "application/javascript"; } else if (req[1] == basereq + ".pck") { filepath += ".pck"; ctype = "application/octet-stream"; @@ -432,6 +435,10 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese } else if (file == "godot.js") { file = p_path.get_file().get_basename() + ".js"; + } else if (file == "godot.worker.js") { + + file = p_path.get_file().get_basename() + ".worker.js"; + } else if (file == "godot.wasm") { file = p_path.get_file().get_basename() + ".wasm"; @@ -563,6 +570,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese // Export generates several files, clean them up on failure. DirAccess::remove_file_or_error(basepath + ".html"); DirAccess::remove_file_or_error(basepath + ".js"); + DirAccess::remove_file_or_error(basepath + ".worker.js"); DirAccess::remove_file_or_error(basepath + ".pck"); DirAccess::remove_file_or_error(basepath + ".png"); DirAccess::remove_file_or_error(basepath + ".wasm"); diff --git a/platform/javascript/id_handler.js b/platform/javascript/id_handler.js index 3851123ed1..67d29075b8 100644 --- a/platform/javascript/id_handler.js +++ b/platform/javascript/id_handler.js @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -var IDHandler = function() { +var IDHandler = /** @constructor */ function() { var ids = {}; var size = 0; diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index 9ba0223387..db90b01f8f 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -470,7 +470,7 @@ void OS_JavaScript::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_s if (p_cursor.is_valid()) { - Map<CursorShape, Vector<Variant> >::Element *cursor_c = cursors_cache.find(p_shape); + Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape); if (cursor_c) { if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) { @@ -887,7 +887,7 @@ int OS_JavaScript::get_current_video_driver() const { void OS_JavaScript::initialize_core() { OS_Unix::initialize_core(); - FileAccess::make_default<FileAccessBufferedFA<FileAccessUnix> >(FileAccess::ACCESS_RESOURCES); + FileAccess::make_default<FileAccessBufferedFA<FileAccessUnix>>(FileAccess::ACCESS_RESOURCES); } Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { @@ -935,6 +935,7 @@ Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, if (p_desired.fullscreen) { /* clang-format off */ EM_ASM({ + const canvas = Module.canvas; (canvas.requestFullscreen || canvas.msRequestFullscreen || canvas.mozRequestFullScreen || canvas.mozRequestFullscreen || canvas.webkitRequestFullscreen diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 5319ea121c..ed59477914 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -52,7 +52,7 @@ class OS_JavaScript : public OS_Unix { Ref<InputEventKey> deferred_key_event; CursorShape cursor_shape; String cursors[CURSOR_MAX]; - Map<CursorShape, Vector<Variant> > cursors_cache; + Map<CursorShape, Vector<Variant>> cursors_cache; Point2 touches[32]; Point2i last_click_pos; diff --git a/platform/javascript/pre.js b/platform/javascript/pre.js deleted file mode 100644 index a870e676ea..0000000000 --- a/platform/javascript/pre.js +++ /dev/null @@ -1,5 +0,0 @@ -var Engine = { - RuntimeEnvironment: function(Module, exposedLibs) { - // The above is concatenated with generated code, and acts as the start of - // a wrapper for said code. See engine.js for the other part of the - // wrapper. diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index 3140d9bac4..e865c3078f 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -124,7 +124,7 @@ public: CursorShape cursor_shape; NSCursor *cursors[CURSOR_MAX]; - Map<CursorShape, Vector<Variant> > cursors_cache; + Map<CursorShape, Vector<Variant>> cursors_cache; MouseMode mouse_mode; String title; @@ -180,7 +180,7 @@ public: } }; - Map<String, Vector<GlobalMenuItem> > global_menus; + Map<String, Vector<GlobalMenuItem>> global_menus; void _update_global_menu(); diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 4c70beee00..8ba8ca8a33 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -1365,7 +1365,7 @@ void OS_OSX::_update_global_menu() { for (int i = 1; i < [main_menu numberOfItems]; i++) { [main_menu removeItemAtIndex:i]; } - for (Map<String, Vector<GlobalMenuItem> >::Element *E = global_menus.front(); E; E = E->next()) { + for (Map<String, Vector<GlobalMenuItem>>::Element *E = global_menus.front(); E; E = E->next()) { if (E->key() != "_dock") { NSMenu *menu = [[[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:E->key().utf8().get_data()]] autorelease]; for (int i = 0; i < E->get().size(); i++) { @@ -1835,7 +1835,7 @@ void OS_OSX::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c if (p_cursor.is_valid()) { - Map<CursorShape, Vector<Variant> >::Element *cursor_c = cursors_cache.find(p_shape); + Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape); if (cursor_c) { if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) { diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index a112f26ac4..041a5bffa6 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -2518,7 +2518,7 @@ void OS_Windows::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shap if (p_cursor.is_valid()) { - Map<CursorShape, Vector<Variant> >::Element *cursor_c = cursors_cache.find(p_shape); + Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape); if (cursor_c) { if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) { diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 6c3769c98c..8506aa7b20 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -232,7 +232,7 @@ class OS_Windows : public OS { HCURSOR cursors[CURSOR_MAX] = { NULL }; CursorShape cursor_shape; - Map<CursorShape, Vector<Variant> > cursors_cache; + Map<CursorShape, Vector<Variant>> cursors_cache; InputDefault *input; JoypadWindows *joypad; diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index c74981fd55..e1f7691cf6 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -2012,8 +2012,10 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { // is correct, but the xorg developers are // not very helpful today. - ::Time tresh = ABSDIFF(peek_event.xkey.time, xkeyevent->time); - if (peek_event.type == KeyPress && tresh < 5) { +#define ABSDIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y))) + ::Time threshold = ABSDIFF(peek_event.xkey.time, xkeyevent->time); +#undef ABSDIFF + if (peek_event.type == KeyPress && threshold < 5) { KeySym rk; XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, NULL); if (rk == keysym_keycode) { @@ -3032,7 +3034,7 @@ void OS_X11::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c if (p_cursor.is_valid()) { - Map<CursorShape, Vector<Variant> >::Element *cursor_c = cursors_cache.find(p_shape); + Map<CursorShape, Vector<Variant>>::Element *cursor_c = cursors_cache.find(p_shape); if (cursor_c) { if (cursor_c->get()[0] == p_cursor && cursor_c->get()[1] == p_hotspot) { diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h index 55d24d64a3..997a6cc053 100644 --- a/platform/x11/os_x11.h +++ b/platform/x11/os_x11.h @@ -179,7 +179,7 @@ class OS_X11 : public OS_Unix { Cursor cursors[CURSOR_MAX]; Cursor null_cursor; CursorShape current_cursor; - Map<CursorShape, Vector<Variant> > cursors_cache; + Map<CursorShape, Vector<Variant>> cursors_cache; InputDefault *input; |