diff --git a/.gitignore b/.gitignore index 805116e..46d75ad 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ # Ignore Gradle build output directory build bin -dist \ No newline at end of file +dist +*.hprof +.idea \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..acc35fb --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "type": "java", + "name": "Current File", + "request": "launch", + "mainClass": "${file}" + }, + { + "type": "java", + "name": "DesktopLauncher", + "request": "launch", + "mainClass": "org.ngengine.platform.DesktopLauncher", + "projectName": "platform-desktop" + } + ] +} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 7f97408..e63f4f3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,288 +1,28 @@ plugins { - id 'application' - id 'org.graalvm.buildtools.native' version '0.10.6' - id 'com.github.johnrengelman.shadow' version '8.1.1' + id 'java' } -repositories { - mavenLocal() - mavenCentral() - maven { - url = uri("https://central.sonatype.com/repository/maven-snapshots") - } - maven { - url = "https://maven.rblb.it/NostrGameEngine/libdatachannel-java" - } -} dependencies { testImplementation libs.junit implementation "org.ngengine:nge-app:${ngeVersion}" - implementation "org.ngengine:nge-app-jvm:${ngeVersion}" implementation "org.ngengine:nge-auth:${ngeVersion}" implementation "org.ngengine:nge-core:${ngeVersion}" implementation "org.ngengine:nge-gui:${ngeVersion}" implementation "org.ngengine:nge-networking:${ngeVersion}" implementation "org.ngengine:nge-ads:${ngeVersion}" - implementation "org.ngengine:jme3-desktop:${ngeVersion}" implementation "org.ngengine:jme3-jbullet:${ngeVersion}" implementation "org.ngengine:jme3-effects:${ngeVersion}" implementation "org.ngengine:jme3-jogg:${ngeVersion}" - implementation "org.ngengine:jme3-lwjgl3:${ngeVersion}" implementation "org.ngengine:jme3-plugins:${ngeVersion}" } + java { - toolchain { - languageVersion = JavaLanguageVersion.of(21) - vendor = JvmVendorSpec.ADOPTIUM - implementation = JvmImplementation.VENDOR_SPECIFIC - } -} - -application { - mainClass = project.findProperty('mainClass') -} - -run { - jvmArgs = [ - "-Djava.util.logging.config.file=${rootProject.projectDir}/dev-logging.properties", - '-ea' - ] - if (getOsName() == 'macos') { - jvmArgs('-XstartOnFirstThread') - } -} - -shadowJar { - archiveBaseName = "${rootProject.name}" - archiveClassifier = 'portable' - archiveVersion = "${project.version}" - destinationDirectory = file("${rootProject.projectDir}/dist/portable") - manifest { - attributes 'Main-Class': application.mainClass.get() - } - mergeServiceFiles() -} - - -build.dependsOn shadowJar - -graalvmNative { - toolchainDetection = true - metadataRepository { - enabled = true - } - binaries { - main { - def buildName = "${rootProject.name}"; - imageName = buildName - def initAtRuntime = file("${project.projectDir}/graal.initialize-at-runtime.conf") - .text - .split("\\r?\\n") - .findAll { it && !it.startsWith('#') && !it.trim().isEmpty() } - .join(',') - - mainClass = application.mainClass.get() - def arguments = [ - '--report-unsupported-elements-at-runtime', - '--install-exit-handlers', - '--add-exports=java.desktop/sun.awt=ALL-UNNAMED', - '--add-exports=java.desktop/sun.java2d=ALL-UNNAMED', - '--add-exports=java.base/sun.nio.ch=ALL-UNNAMED', - '-H:+ReportExceptionStackTraces', - '-H:+PrintClassInitialization', - '--initialize-at-run-time=' + initAtRuntime, - '--no-server', - '--enable-url-protocols=http,https,file', - '--link-at-build-time', - '-H:+UnlockExperimentalVMOptions', - '--enable-native-access=ALL-UNNAMED', - '-H:+StaticExecutableWithDynamicLibC', - '-march=compatibility', - '-Djava.awt.headless=true', - '-H:IncludeResources=.*', - '-H:Name=' + buildName, - "-H:ConfigurationFileDirectories=${project.projectDir}/trace" - ] - - // Add G1 GC only if linux - if (org.gradle.internal.os.OperatingSystem.current().isLinux()) { - arguments.add('--gc=G1') - } - - buildArgs.addAll(arguments) - sharedLibrary = false - } - } -} - -configurations { - nativeImageCompileOnly { - canBeResolved = true - } -} - -def getOsName(){ - def osName = org.gradle.internal.os.OperatingSystem.current().getName().toLowerCase() - - if (org.gradle.internal.os.OperatingSystem.current().isWindows()){ - osName = "windows" - } else if (org.gradle.internal.os.OperatingSystem.current().isMacOsX()) { - osName = "macos" - } else if (org.gradle.internal.os.OperatingSystem.current().isLinux()) { - osName = "linux" - } - - return osName -} - - -task traceNative(type: JavaExec) { - group = 'graalvm' - description = 'Trace reflection and resource usage' - mainClass = application.mainClass.get() - classpath = sourceSets.main.runtimeClasspath - jvmArgs = [ - "-agentlib:native-image-agent=config-merge-dir=${project.projectDir}/trace", - '-Djava.awt.headless=true', - ] - - if (getOsName() == 'macos') { - jvmArgs('-XstartOnFirstThread') - } - - systemProperty 'java.awt.headless', 'false' - systemProperty 'testMode', 'true' - doFirst { - println "Tracing to: ${project.projectDir}/trace" - println "Run the application and exercise ALL features:" - println "- Load different asset types (textures, models, sounds)" - println "- Use UI components" - println "- Trigger network operations" - println "- Test different game states" - println "Then close the application normally." - } - doLast { - println "Tracing complete! Generated/updated files:" - fileTree("${project.projectDir}/trace").each { file -> - if (file.isFile()) { - println " - ${file.name} (${file.length()} bytes)" - } - } - } -} - - -task buildNativeExecutable { - group = 'graalvm' - description = 'Build native executable for current platform' - dependsOn 'nativeCompile' - doLast { - def buildDirNative = "${project.buildDir}/native/nativeCompile" - def executableName = graalvmNative.binaries.main.imageName.get() - if (org.gradle.internal.os.OperatingSystem.current().isWindows()) { - executableName += '.exe' - } - - def osName = getOsName() - def osArch = System.getProperty("os.arch").toLowerCase() - def buildName = "${rootProject.name}-${project.version}-${osName}-${osArch}"; - - def distDir = file("${rootProject.projectDir}/dist/${buildName}.buildNative") - distDir.mkdirs() - - copy { - from buildDirNative - into distDir - exclude '**/reports/**' - exclude '**/*.report' - exclude '**/*.dmp' - exclude '**/*.log' - } - - println "Native executable built: ${buildDirNative}/${executableName}" - println "All native build files copied to: ${distDir}" - } -} - - - - def jpackage(installerType, distDir){ - def os = org.gradle.internal.os.OperatingSystem.current() - - - - def jarFile = shadowJar.archiveFile.get().asFile - def jarDir = jarFile.parentFile - def mainClass = application.mainClass.get() - - - def shadowJarFile = shadowJar.archiveFile.get().asFile - - def commandLine = [ - 'jpackage', - '--input', jarDir.absolutePath, - '--main-jar', jarFile.name, - '--main-class', mainClass, - '--name', rootProject.name, - '--app-version', project.version, - '--type', installerType, - '--dest', distDir, - '--java-options', project.findProperty('javaOptions') ?: '', - '--copyright', project.findProperty('copyright') - ] - - // Optional: platform-specific flags - if (os.isWindows()) { - commandLine += ['--win-menu', '--win-shortcut'] - } else if (os.isMacOsX()) { - commandLine += ['--icon', "${rootProject.projectDir}/icon.icns"] - } else if (os.isLinux()) { - commandLine += ['--icon', "${rootProject.projectDir}/icon.png"] - } - return commandLine -} - - - -task buildInstaller(type: DefaultTask) { - group = 'distribution' - description = 'Create native installer using jpackage' - dependsOn shadowJar - - def os = org.gradle.internal.os.OperatingSystem.current() - def osName = getOsName() - def osArch = System.getProperty("os.arch").toLowerCase() - def buildName = "${rootProject.name}-${project.version}-${osName}-${osArch}-SETUP"; - def distDir = file("${rootProject.projectDir}/dist/${buildName}.setup") - - - doLast { - if (distDir.exists()) { - println "Deleting existing output directory: ${distDir}" - delete distDir - } - - if(os.isWindows()){ - exec { - commandLine jpackage('exe', distDir) - } - } else if(os.isMacOsX()){ - exec { - commandLine jpackage('dmg', distDir) - } - } else if(os.isLinux()){ - exec { - commandLine jpackage('deb', distDir) - } - exec { - commandLine jpackage('rpm', distDir) - } - } - } -} + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} \ No newline at end of file diff --git a/app/src/main/java/org/ngengine/demo/adc/AdcDemo.java b/app/src/main/java/org/ngengine/app/Main.java similarity index 68% rename from app/src/main/java/org/ngengine/demo/adc/AdcDemo.java rename to app/src/main/java/org/ngengine/app/Main.java index 6b6baf5..77a00fb 100644 --- a/app/src/main/java/org/ngengine/demo/adc/AdcDemo.java +++ b/app/src/main/java/org/ngengine/app/Main.java @@ -1,17 +1,26 @@ -package org.ngengine.demo.adc; +package org.ngengine.app; import org.ngengine.NGEApplication; +import org.ngengine.NGEApplication.NGEAppRunner; import org.ngengine.ads.ImmersiveAdComponent; import org.ngengine.components.ComponentManager; +import org.ngengine.demo.adc.CharacterComponent; +import org.ngengine.demo.adc.HudComponent; +import org.ngengine.demo.adc.LoadingScreenComponent; +import org.ngengine.demo.adc.MapComponent; +import org.ngengine.demo.adc.PhysicsComponent; +import org.ngengine.demo.adc.PostprocessingComponent; +import org.ngengine.demo.adc.SoundComponent; import org.ngengine.gui.win.NWindowManagerComponent; +import org.ngengine.nostr4j.keypair.NostrPublicKey; import com.jme3.system.AppSettings; -public class AdcDemo { +public class Main { - public static void main(String arg[]){ + public static NGEAppRunner main(String arg[]){ AppSettings settings = new AppSettings(true); settings.setRenderer(AppSettings.LWJGL_OPENGL32); settings.setWidth(1280); @@ -24,13 +33,14 @@ public class AdcDemo { settings.setGraphicsDebug(false); settings.setX11PlatformPreferred(true); settings.setTitle("Nostr Game Engine Demo"); - - + settings.setEmulateMouse(true); + settings.setEmulateKeyboard(true); + + NostrPublicKey appId = NostrPublicKey.fromBech32("npub1tc32kq8hr992tzvt09rwuc5wk6aa08pquqawasv20acjp4umny5q6axr4t"); - Runnable appBuilder = NGEApplication.createApp(settings, app -> { + NGEAppRunner appRunner = NGEApplication.createApp(appId, settings, app -> { ImmersiveAdComponent dep = app.enableAds(); - ComponentManager mng = app.getComponentManager(); mng.addAndEnableComponent(new LoadingScreenComponent(), null, new Object[] { NWindowManagerComponent.class }); @@ -50,6 +60,6 @@ public class AdcDemo { }); - appBuilder.run(); + return appRunner; } } diff --git a/app/src/main/java/org/ngengine/demo/adc/MapComponent.java b/app/src/main/java/org/ngengine/demo/adc/MapComponent.java index 2e6e55e..5dbd7f0 100644 --- a/app/src/main/java/org/ngengine/demo/adc/MapComponent.java +++ b/app/src/main/java/org/ngengine/demo/adc/MapComponent.java @@ -51,6 +51,7 @@ public class MapComponent implements Component, AsyncAssetLoadingFragmen if(map==null){ System.out.println("AdCity: Loading map from assets..."); map = assetManager.loadModel("adc/city/city.gltf"); + // map = new Node(); PhysicsComponent physics = mng.getComponent(PhysicsComponent.class); map.depthFirstTraversal(sx->{ Object worldGuard = sx.getUserData("nge.worldguard"); diff --git a/gradle.properties b/gradle.properties index 1442e20..135e75e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,20 @@ +# Gradle settings org.gradle.configuration-cache=false +org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true -ngeVersion=0.1.0-SNAPSHOT +# NGE versions +ngePlatformVersion=0.1.3 +ngeVersion=0.1 + +# Build targets +withDesktopPlatform=true +withAndroidPlatform=false + +# Project settings version=1.0.0 +versionCode=1 projectName=adcity -mainClass=org.ngengine.demo.adc.AdcDemo +applicationId=org.ngengine.demo.AdcDemo copyright=Copyright (c) 2025 -javaOptions= \ No newline at end of file +javaOptions= diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f85790b..466d564 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,9 +2,26 @@ # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format [versions] +androidxJunit = "1.3.0" +appcompat = "1.7.1" +constraintlayout = "2.2.1" +espressoCore = "3.7.0" guava = "33.3.1-jre" junit = "4.13.2" +junitVersion = "4.13.3-SNAPSHOT" +material = "1.12.0" +ngePlatformAndroid = "0.1.0-SNAPSHOT" +navigationFragment = "2.6.0" +navigationUi = "2.6.0" [libraries] +appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } +constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" } +espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" } +ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidxJunit" } guava = { module = "com.google.guava:guava", version.ref = "guava" } junit = { module = "junit:junit", version.ref = "junit" } +junit-v4133snapshot = { module = "junit:junit", version.ref = "junitVersion" } +material = { module = "com.google.android.material:material", version.ref = "material" } +navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment", version.ref = "navigationFragment" } +navigation-ui = { group = "androidx.navigation", name = "navigation-ui", version.ref = "navigationUi" } diff --git a/local.properties b/local.properties new file mode 100644 index 0000000..27d9bd5 --- /dev/null +++ b/local.properties @@ -0,0 +1,8 @@ +## This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Mon Sep 01 09:13:24 CEST 2025 +sdk.dir=/home/riccardobl/Android/Sdk diff --git a/platform-android/build.gradle b/platform-android/build.gradle new file mode 100644 index 0000000..b9f5cb3 --- /dev/null +++ b/platform-android/build.gradle @@ -0,0 +1,58 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdk 36 + namespace "${project.applicationId}" + + defaultConfig { + applicationId "${project.applicationId}" + minSdk 33 + targetSdk 33 + versionCode project.versionCode.toInteger() + versionName project.version + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + packagingOptions { + jniLibs { + useLegacyPackaging = true + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + buildFeatures { + viewBinding true + } + +} + + +dependencies { + implementation libs.appcompat + implementation libs.material + implementation libs.constraintlayout + implementation libs.navigation.fragment + implementation libs.navigation.ui + testImplementation libs.junit.v4133snapshot + androidTestImplementation libs.ext.junit + androidTestImplementation libs.espresso.core + + + implementation project(':app') + implementation "org.ngengine:nge-platform-android:${ngePlatformVersion}" + implementation "org.ngengine:nge-app:${ngeVersion}" + implementation "org.ngengine:jme3-android:${ngeVersion}" + implementation "org.ngengine:jme3-android-native:${ngeVersion}" + +} diff --git a/platform-android/src/androidTest/java/.gitkeep b/platform-android/src/androidTest/java/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/platform-android/src/main/AndroidManifest.xml b/platform-android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..5a62138 --- /dev/null +++ b/platform-android/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform-android/src/main/java/org/ngengine/platform/AndroidLauncherActivity.java b/platform-android/src/main/java/org/ngengine/platform/AndroidLauncherActivity.java new file mode 100644 index 0000000..d0a8cfd --- /dev/null +++ b/platform-android/src/main/java/org/ngengine/platform/AndroidLauncherActivity.java @@ -0,0 +1,36 @@ +package org.ngengine.platform; + +import android.os.Bundle; + +import com.google.android.material.snackbar.Snackbar; + +import androidx.appcompat.app.AppCompatActivity; + +import android.view.View; + +import androidx.navigation.NavController; +import androidx.navigation.Navigation; +import androidx.navigation.ui.AppBarConfiguration; +import androidx.navigation.ui.NavigationUI; + + +import org.ngengine.demo.AdcDemo.R; + +public class AndroidLauncherActivity extends AppCompatActivity { + + private AndroidLauncherFragment launcherFragment; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_android_launcher); + + launcherFragment = new AndroidLauncherFragment(); + getSupportFragmentManager() + .beginTransaction() + .add(R.id.fragment_container, launcherFragment) + .commit(); + } + + +} \ No newline at end of file diff --git a/platform-android/src/main/java/org/ngengine/platform/AndroidLauncherFragment.java b/platform-android/src/main/java/org/ngengine/platform/AndroidLauncherFragment.java new file mode 100644 index 0000000..cfebea4 --- /dev/null +++ b/platform-android/src/main/java/org/ngengine/platform/AndroidLauncherFragment.java @@ -0,0 +1,308 @@ + +package org.ngengine.platform; + +import android.content.Context; +import android.opengl.GLSurfaceView; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.fragment.app.Fragment; + +import com.jme3.audio.AudioRenderer; +import com.jme3.input.JoyInput; +import com.jme3.input.android.AndroidSensorJoyInput; + +import com.jme3.system.AppSettings; +import com.jme3.system.SystemListener; +import com.jme3.system.android.JmeAndroidSystem; +import com.jme3.system.android.OGLESContext; +import com.jme3.util.AndroidLogHandler; +import com.jme3.util.AndroidNativeBufferAllocator; +import com.jme3.util.BufferAllocatorFactory; + +import android.app.AlertDialog; +import org.ngengine.NGEApplication.NGEAppRunner; + +import org.ngengine.NGEApplication; +import org.ngengine.app.Main; +import org.ngengine.platform.android.AndroidThreadedPlatform; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + + +public class AndroidLauncherFragment extends Fragment implements + SystemListener { + private static final Logger logger = Logger.getLogger(AndroidLauncherFragment.class.getName()); + + protected GLSurfaceView view = null; + + private NGEApplication app = null; + private boolean finishOnAppStop = true; + + + @Override + public void onAttach(Context context) { + super.onAttach(context); + } + + public void setCloseActivityOnAppStop(boolean close){ + this.finishOnAppStop = close; + } + + /** + * onCreate is called once for this Fragment instance. + * Note: AppSettings configuration has been removed. Configure settings in your app. + */ + @Override + @SuppressWarnings("unchecked") + public void onCreate(Bundle savedInstanceState) { + initializeLogHandler(); + logger.fine("onCreate"); + super.onCreate(savedInstanceState); + + System.setProperty( BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION, AndroidNativeBufferAllocator.class.getName()); + NGEPlatform.set(new AndroidThreadedPlatform(this.getContext())); + NGEAppRunner runner = Main.main(new String[0]); + app = runner.app(); + + AppSettings settings = app.getSettings(); + settings.setAudioRenderer(AppSettings.ANDROID_OPENAL_SOFT); + + app.start(); + + + OGLESContext ctx = (OGLESContext) app.getJme3App().getContext(); + ctx.setSystemListener(this); + } + + /** + * Create the GLSurfaceView and return it as the Fragment's view. + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + logger.fine("onCreateView"); + view = ((OGLESContext) app.getJme3App().getContext()).createView(requireActivity()); + JmeAndroidSystem.setView(view); + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + logger.fine("onActivityCreated"); + super.onActivityCreated(savedInstanceState); + } + + @Override + public void onStart() { + logger.fine("onStart"); + super.onStart(); + } + + /** + * When the Fragment resumes, call gainFocus() in the jME application. + */ + @Override + public void onResume() { + logger.fine("onResume"); + super.onResume(); + gainFocus(); + } + + /** + * When the Fragment pauses, call loseFocus() in the jME application. + */ + @Override + public void onPause() { + logger.fine("onPause"); + loseFocus(); + super.onPause(); + } + + @Override + public void onStop() { + logger.fine("onStop"); + super.onStop(); + } + + /** + * Clear references to the GLSurfaceView. + */ + @Override + public void onDestroyView() { + logger.fine("onDestroyView"); + if (view != null) { + if (view.getParent() instanceof ViewGroup) { + ((ViewGroup) view.getParent()).removeView(view); + } + } + view = null; + JmeAndroidSystem.setView(null); + super.onDestroyView(); + } + + /** + * Called by the system when the application is being destroyed. + */ + @Override + public void onDestroy() { + logger.fine("onDestroy"); + if (app != null) { + app.stop(); + } + app = null; + super.onDestroy(); + } + + /** + * Called when an error has occurred. Shows an error message and logs the exception. + */ + @Override + public void handleError(final String errorMsg, final Throwable t) { + String stackTrace = ""; + String title = "Error"; + + if (t != null) { + // Convert exception to string + StringWriter sw = new StringWriter(100); + t.printStackTrace(new PrintWriter(sw)); + stackTrace = sw.toString(); + title = t.toString(); + } + + final String finalTitle = title; + final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception") + + "\n" + stackTrace; + + logger.log(Level.SEVERE, finalMsg); + + requireActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity()); + builder.setTitle(finalTitle); + builder.setMessage(finalMsg); + + AlertDialog dialog = builder.create(); + dialog.show(); + } + }); + } + + + + + /** + * Removes the standard Android log handler due to an issue with not logging + * entries lower than INFO level and adds a handler that produces + * JME formatted log messages. + */ + protected void initializeLogHandler() { + Logger log = LogManager.getLogManager().getLogger(""); + for (Handler handler : log.getHandlers()) { + if (log.getLevel() != null && log.getLevel().intValue() <= Level.FINE.intValue()) { + Log.v("AndroidHarness", "Removing Handler class: " + handler.getClass().getName()); + } + log.removeHandler(handler); + } + Handler handler = new AndroidLogHandler(); + log.addHandler(handler); + handler.setLevel(Level.ALL); + } + + @Override + public void initialize() { + app.getJme3App().initialize(); + + } + + @Override + public void reshape(int width, int height) { + app.getJme3App().reshape(width, height); + } + + @Override + public void rescale(float x, float y) { + app.getJme3App().rescale(x, y); + } + + @Override + public void update() { + app.getJme3App().update(); + } + + @Override + public void requestClose(boolean esc) { + app.getJme3App().requestClose(esc); + } + + @Override + public void destroy() { + if (app != null) { + app.getJme3App().destroy(); + } + if (finishOnAppStop) { + requireActivity().finish(); + } + } + + @Override + public void gainFocus() { + logger.fine("gainFocus"); + if (view != null) { + view.onResume(); + } + + if (app != null) { + // resume the audio + AudioRenderer audioRenderer = app.getJme3App().getAudioRenderer(); + if (audioRenderer != null) { + audioRenderer.resumeAll(); + } + // resume the sensors (aka joysticks) + if (app.getJme3App().getContext() != null) { + JoyInput joyInput = app.getJme3App().getContext().getJoyInput(); + if (joyInput instanceof AndroidSensorJoyInput) { + ((AndroidSensorJoyInput) joyInput).resumeSensors(); + } + } + app.getJme3App().gainFocus(); + } + } + + @Override + public void loseFocus() { + logger.fine("loseFocus"); + if (app != null) { + app.getJme3App().loseFocus(); + } + + if (view != null) { + view.onPause(); + } + + if (app != null) { + // pause the audio + AudioRenderer audioRenderer = app.getJme3App().getAudioRenderer(); + if (audioRenderer != null) { + audioRenderer.pauseAll(); + } + // pause the sensors (aka joysticks) + if (app.getJme3App().getContext() != null) { + JoyInput joyInput = app.getJme3App().getContext().getJoyInput(); + if (joyInput instanceof AndroidSensorJoyInput) { + ((AndroidSensorJoyInput) joyInput).pauseSensors(); + } + } + } + } + + +} \ No newline at end of file diff --git a/platform-android/src/main/res/drawable-v24/ic_launcher_foreground.xml b/platform-android/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..7706ab9 --- /dev/null +++ b/platform-android/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + diff --git a/platform-android/src/main/res/drawable/ic_launcher_background.xml b/platform-android/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/platform-android/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform-android/src/main/res/layout/activity_android_launcher.xml b/platform-android/src/main/res/layout/activity_android_launcher.xml new file mode 100644 index 0000000..25b2cb1 --- /dev/null +++ b/platform-android/src/main/res/layout/activity_android_launcher.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/platform-android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/platform-android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6b78462 --- /dev/null +++ b/platform-android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/platform-android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/platform-android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6b78462 --- /dev/null +++ b/platform-android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/platform-android/src/main/res/mipmap-hdpi/ic_launcher.webp b/platform-android/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..3e37e73 Binary files /dev/null and b/platform-android/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/platform-android/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/platform-android/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..992a775 Binary files /dev/null and b/platform-android/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/platform-android/src/main/res/mipmap-mdpi/ic_launcher.webp b/platform-android/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..60e2f8c Binary files /dev/null and b/platform-android/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/platform-android/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/platform-android/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..04c69c0 Binary files /dev/null and b/platform-android/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/platform-android/src/main/res/mipmap-xhdpi/ic_launcher.webp b/platform-android/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..13410d7 Binary files /dev/null and b/platform-android/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/platform-android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/platform-android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..554b15e Binary files /dev/null and b/platform-android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/platform-android/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/platform-android/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..fe6bf91 Binary files /dev/null and b/platform-android/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/platform-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/platform-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..f8baf99 Binary files /dev/null and b/platform-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/platform-android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/platform-android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..698b15c Binary files /dev/null and b/platform-android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/platform-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/platform-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..4f3ce06 Binary files /dev/null and b/platform-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/platform-android/src/test/java/.gitkeep b/platform-android/src/test/java/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/platform-desktop/build.gradle b/platform-desktop/build.gradle new file mode 100644 index 0000000..abf953e --- /dev/null +++ b/platform-desktop/build.gradle @@ -0,0 +1,268 @@ + +plugins { + id 'application' + id 'org.graalvm.buildtools.native' version '0.10.6' + id 'com.github.johnrengelman.shadow' version '8.1.1' +} + + + +dependencies { + testImplementation libs.junit + implementation "org.ngengine:nge-platform-jvm:${ngePlatformVersion}" + implementation "org.ngengine:nge-app:${ngeVersion}" + implementation "org.ngengine:jme3-desktop:${ngeVersion}" + implementation "org.ngengine:jme3-lwjgl3:${ngeVersion}" + implementation project(':app') +} + + +application { + mainClass = "org.ngengine.platform.DesktopLauncher" +} + +run { + jvmArgs = [ + "-Djava.util.logging.config.file=${rootProject.projectDir}/dev-logging.properties", + '-ea' + ] + if (getOsName() == 'macos') { + jvmArgs('-XstartOnFirstThread') + } +} + +shadowJar { + archiveBaseName = "${rootProject.name}" + archiveClassifier = 'portable' + archiveVersion = "${project.version}" + destinationDirectory = file("${rootProject.projectDir}/dist/portable") + manifest { + attributes 'Main-Class': application.mainClass.get() + } + mergeServiceFiles() +} + + +build.dependsOn shadowJar + +graalvmNative { + toolchainDetection = true + metadataRepository { + enabled = true + } + binaries { + main { + def buildName = "${rootProject.name}"; + imageName = buildName + def initAtRuntime = file("${project.projectDir}/graal.initialize-at-runtime.conf") + .text + .split("\\r?\\n") + .findAll { it && !it.startsWith('#') && !it.trim().isEmpty() } + .join(',') + + mainClass = application.mainClass.get() + def arguments = [ + '--report-unsupported-elements-at-runtime', + '--install-exit-handlers', + '--add-exports=java.desktop/sun.awt=ALL-UNNAMED', + '--add-exports=java.desktop/sun.java2d=ALL-UNNAMED', + '--add-exports=java.base/sun.nio.ch=ALL-UNNAMED', + '-H:+ReportExceptionStackTraces', + '-H:+PrintClassInitialization', + '--initialize-at-run-time=' + initAtRuntime, + '--no-server', + '--enable-url-protocols=http,https,file', + '--link-at-build-time', + '-H:+UnlockExperimentalVMOptions', + '--enable-native-access=ALL-UNNAMED', + '-H:+StaticExecutableWithDynamicLibC', + '-march=compatibility', + '-Djava.awt.headless=true', + '-H:IncludeResources=.*', + '-H:Name=' + buildName, + "-H:ConfigurationFileDirectories=${project.projectDir}/trace" + ] + + // Add G1 GC only if linux + if (org.gradle.internal.os.OperatingSystem.current().isLinux()) { + arguments.add('--gc=G1') + } + + buildArgs.addAll(arguments) + sharedLibrary = false + } + } +} + +configurations { + nativeImageCompileOnly { + canBeResolved = true + } +} + +def getOsName(){ + def osName = org.gradle.internal.os.OperatingSystem.current().getName().toLowerCase() + + if (org.gradle.internal.os.OperatingSystem.current().isWindows()){ + osName = "windows" + } else if (org.gradle.internal.os.OperatingSystem.current().isMacOsX()) { + osName = "macos" + } else if (org.gradle.internal.os.OperatingSystem.current().isLinux()) { + osName = "linux" + } + + return osName +} + + +task traceNative(type: JavaExec) { + group = 'graalvm' + description = 'Trace reflection and resource usage' + mainClass = application.mainClass.get() + classpath = sourceSets.main.runtimeClasspath + jvmArgs = [ + "-agentlib:native-image-agent=config-merge-dir=${project.projectDir}/trace", + '-Djava.awt.headless=true', + ] + + if (getOsName() == 'macos') { + jvmArgs('-XstartOnFirstThread') + } + + systemProperty 'java.awt.headless', 'false' + systemProperty 'testMode', 'true' + doFirst { + println "Tracing to: ${project.projectDir}/trace" + println "Run the application and exercise ALL features:" + println "- Load different asset types (textures, models, sounds)" + println "- Use UI components" + println "- Trigger network operations" + println "- Test different game states" + println "Then close the application normally." + } + doLast { + println "Tracing complete! Generated/updated files:" + fileTree("${project.projectDir}/trace").each { file -> + if (file.isFile()) { + println " - ${file.name} (${file.length()} bytes)" + } + } + } +} + + +task buildNativeExecutable { + group = 'graalvm' + description = 'Build native executable for current platform' + dependsOn 'nativeCompile' + doLast { + def buildDirNative = "${project.buildDir}/native/nativeCompile" + def executableName = graalvmNative.binaries.main.imageName.get() + if (org.gradle.internal.os.OperatingSystem.current().isWindows()) { + executableName += '.exe' + } + + def osName = getOsName() + def osArch = System.getProperty("os.arch").toLowerCase() + def buildName = "${rootProject.name}-${project.version}-${osName}-${osArch}"; + + def distDir = file("${rootProject.projectDir}/dist/${buildName}.buildNative") + distDir.mkdirs() + + copy { + from buildDirNative + into distDir + exclude '**/reports/**' + exclude '**/*.report' + exclude '**/*.dmp' + exclude '**/*.log' + } + + println "Native executable built: ${buildDirNative}/${executableName}" + println "All native build files copied to: ${distDir}" + } +} + + + + def jpackage(installerType, distDir){ + def os = org.gradle.internal.os.OperatingSystem.current() + + + + def jarFile = shadowJar.archiveFile.get().asFile + def jarDir = jarFile.parentFile + def mainClass = application.mainClass.get() + + + def shadowJarFile = shadowJar.archiveFile.get().asFile + + def commandLine = [ + 'jpackage', + '--input', jarDir.absolutePath, + '--main-jar', jarFile.name, + '--main-class', mainClass, + '--name', rootProject.name, + '--app-version', project.version, + '--type', installerType, + '--dest', distDir, + '--java-options', project.findProperty('javaOptions') ?: '', + '--copyright', project.findProperty('copyright') + ] + + // Optional: platform-specific flags + if (os.isWindows()) { + commandLine += ['--win-menu', '--win-shortcut'] + } else if (os.isMacOsX()) { + commandLine += ['--icon', "${rootProject.projectDir}/icon.icns"] + } else if (os.isLinux()) { + commandLine += ['--icon', "${rootProject.projectDir}/icon.png"] + } + return commandLine +} + + + +task buildInstaller(type: DefaultTask) { + group = 'distribution' + description = 'Create native installer using jpackage' + dependsOn shadowJar + + def os = org.gradle.internal.os.OperatingSystem.current() + def osName = getOsName() + def osArch = System.getProperty("os.arch").toLowerCase() + def buildName = "${rootProject.name}-${project.version}-${osName}-${osArch}-SETUP"; + def distDir = file("${rootProject.projectDir}/dist/${buildName}.setup") + + + doLast { + if (distDir.exists()) { + println "Deleting existing output directory: ${distDir}" + delete distDir + } + + if(os.isWindows()){ + exec { + commandLine jpackage('exe', distDir) + } + } else if(os.isMacOsX()){ + exec { + commandLine jpackage('dmg', distDir) + } + } else if(os.isLinux()){ + exec { + commandLine jpackage('deb', distDir) + } + exec { + commandLine jpackage('rpm', distDir) + } + } + } +} + + + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} \ No newline at end of file diff --git a/app/graal.initialize-at-runtime.conf b/platform-desktop/graal.initialize-at-runtime.conf similarity index 100% rename from app/graal.initialize-at-runtime.conf rename to platform-desktop/graal.initialize-at-runtime.conf diff --git a/platform-desktop/src/main/java/org/ngengine/platform/.gitkeep b/platform-desktop/src/main/java/org/ngengine/platform/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/platform-desktop/src/main/java/org/ngengine/platform/DesktopLauncher.java b/platform-desktop/src/main/java/org/ngengine/platform/DesktopLauncher.java new file mode 100644 index 0000000..05bd0cd --- /dev/null +++ b/platform-desktop/src/main/java/org/ngengine/platform/DesktopLauncher.java @@ -0,0 +1,14 @@ +package org.ngengine.platform; + +import org.ngengine.app.Main; +import org.ngengine.platform.jvm.JVMAsyncPlatform; +import org.ngengine.NGEApplication.NGEAppRunner; + +public class DesktopLauncher { + public static void main(String[] args) { + NGEPlatform.set(new JVMAsyncPlatform()); + NGEAppRunner runner = Main.main(args); + runner.start(); + } + +} diff --git a/platform-desktop/src/main/resources/.gitkeep b/platform-desktop/src/main/resources/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/platform-desktop/src/test/java/.gitkeep b/platform-desktop/src/test/java/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/trace/jni-config.json b/platform-desktop/trace/jni-config.json similarity index 100% rename from app/trace/jni-config.json rename to platform-desktop/trace/jni-config.json diff --git a/app/trace/predefined-classes-config.json b/platform-desktop/trace/predefined-classes-config.json similarity index 100% rename from app/trace/predefined-classes-config.json rename to platform-desktop/trace/predefined-classes-config.json diff --git a/app/trace/proxy-config.json b/platform-desktop/trace/proxy-config.json similarity index 100% rename from app/trace/proxy-config.json rename to platform-desktop/trace/proxy-config.json diff --git a/app/trace/reachability-metadata.json b/platform-desktop/trace/reachability-metadata.json similarity index 100% rename from app/trace/reachability-metadata.json rename to platform-desktop/trace/reachability-metadata.json diff --git a/app/trace/reflect-config.json b/platform-desktop/trace/reflect-config.json similarity index 100% rename from app/trace/reflect-config.json rename to platform-desktop/trace/reflect-config.json diff --git a/app/trace/resource-config.json b/platform-desktop/trace/resource-config.json similarity index 100% rename from app/trace/resource-config.json rename to platform-desktop/trace/resource-config.json diff --git a/app/trace/serialization-config.json b/platform-desktop/trace/serialization-config.json similarity index 100% rename from app/trace/serialization-config.json rename to platform-desktop/trace/serialization-config.json diff --git a/settings.gradle b/settings.gradle index 8e986b1..2903720 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,44 @@ -plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.9.0' + +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.9.0' + id("com.android.application") version '8.12.2' apply false + id("com.android.library") version '8.12.2' apply false + id("org.jetbrains.kotlin.android") version "2.1.20" apply false + } } + + + + + +dependencyResolutionManagement { + repositories { + mavenLocal() + mavenCentral() + google() + maven { + url = uri("https://central.sonatype.com/repository/maven-snapshots") + } + } +} + + + rootProject.name = settings.projectName include('app') + +if(withDesktopPlatform) { + include('platform-desktop') +} + +if (withAndroidPlatform) { + include('platform-android') +} \ No newline at end of file