Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ checkstyle = "13.3.0"
jacoco = "0.8.12"
lwjgl3 = "3.4.1"
angle = "2026-05-09"
libjglios = "0.4"
saferalloc = "0.0.8"
libjglios = "0.6"
saferalloc = "0.0.10"
nifty = "1.4.3"
spotbugs = "4.9.8"
jmeAndroidNatives = "3.10.0-xt16kb"
jmeAndroidNatives = "3.10.0-xt16kb-alloc"

[libraries]

Expand Down Expand Up @@ -73,9 +73,10 @@ saferalloc-natives-windows-aarch64 = { module = "org.ngengine:saferalloc-natives
saferalloc-natives-macos-x8664 = { module = "org.ngengine:saferalloc-natives-macos-x86_64", version.ref = "saferalloc" }
saferalloc-natives-macos-aarch64 = { module = "org.ngengine:saferalloc-natives-macos-aarch64", version.ref = "saferalloc" }
saferalloc-natives-android = { module = "org.ngengine:saferalloc-natives-android", version.ref = "saferalloc" }
saferalloc-natives-ios = { module = "org.ngengine:saferalloc-natives-ios", version.ref = "saferalloc" }

[bundles]
saferalloc = ["saferalloc", "saferalloc-natives-linux-x8664", "saferalloc-natives-linux-aarch64", "saferalloc-natives-windows-x8664", "saferalloc-natives-windows-aarch64", "saferalloc-natives-macos-x8664", "saferalloc-natives-macos-aarch64", "saferalloc-natives-android"]
saferalloc = ["saferalloc", "saferalloc-natives-linux-x8664", "saferalloc-natives-linux-aarch64", "saferalloc-natives-windows-x8664", "saferalloc-natives-windows-aarch64", "saferalloc-natives-macos-x8664", "saferalloc-natives-macos-aarch64", "saferalloc-natives-android", "saferalloc-natives-ios"]

[plugins]
jacoco = { id = "jacoco", version.ref = "jacoco" }
1 change: 1 addition & 0 deletions jme3-android-examples/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ dependencies {
implementation project(':jme3-plugins')
implementation project(':jme3-plugins-json')
implementation project(':jme3-plugins-json-gson')
implementation project(':jme3-saferallocator')
implementation project(':jme3-terrain')
implementation files(examplesJar.flatMap { it.archiveFile })
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
*/
public abstract class AndroidHarnessFragment extends Fragment implements SystemListener {
private static final Logger logger = Logger.getLogger(AndroidHarnessFragment.class.getName());
private static final String SAFER_BUFFER_ALLOCATOR_CLASS = "com.jme3.util.SaferBufferAllocator";

protected GLSurfaceView view;
protected LegacyApplication app;
Expand All @@ -90,9 +91,12 @@ public void onCreate(Bundle savedInstanceState) {
logger.fine("onCreate");
super.onCreate(savedInstanceState);

System.setProperty(
BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION,
AndroidNativeBufferAllocator.class.getName());
if (System.getProperty(BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION) == null) {
String allocator = isClassPresent(SAFER_BUFFER_ALLOCATOR_CLASS)
? SAFER_BUFFER_ALLOCATOR_CLASS
: AndroidNativeBufferAllocator.class.getName();
System.setProperty(BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION, allocator);
}

try {
app = createApplication();
Expand All @@ -112,6 +116,15 @@ public void onCreate(Bundle savedInstanceState) {
*/
protected abstract LegacyApplication createApplication() throws Exception;

private static boolean isClassPresent(String className) {
try {
Class.forName(className, false, AndroidHarnessFragment.class.getClassLoader());
return true;
} catch (Throwable ignored) {
return false;
}
}


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,37 +31,104 @@
*/
package com.jme3.util;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Allocates and destroys direct byte buffers using native code.
*
* @author pavl_g.
*/
public final class AndroidNativeBufferAllocator implements BufferAllocator {
private static final Logger LOGGER = Logger.getLogger(AndroidNativeBufferAllocator.class.getName());
private static final ReferenceQueue<ByteBuffer> REFERENCE_QUEUE = new ReferenceQueue<>();
private static final Map<Long, Deallocator> DEALLOCATORS = new ConcurrentHashMap<>();
private static final Thread CLEAN_THREAD = new Thread(AndroidNativeBufferAllocator::freeCollectedBuffers);

static {
System.loadLibrary("bufferallocatorjme");
CLEAN_THREAD.setDaemon(true);
CLEAN_THREAD.setName("Android Native Buffer Deallocator");
CLEAN_THREAD.start();
}

@Override
public void destroyDirectBuffer(Buffer toBeDestroyed) {
releaseDirectByteBuffer(toBeDestroyed);
long address = directBufferAddress(toBeDestroyed);
if (address == 0L) {
LOGGER.log(Level.WARNING, "Not found address of the {0}", toBeDestroyed);
return;
}
Deallocator deallocator = DEALLOCATORS.remove(address);
if (deallocator == null) {
LOGGER.log(Level.WARNING, "Not found a deallocator for address {0}", address);
return;
}
deallocator.freeNow();
}

@Override
public ByteBuffer allocate(int size) {
return createDirectByteBuffer(size);
ByteBuffer buffer = createDirectByteBuffer(size);
if (buffer == null) {
throw new OutOfMemoryError("Could not allocate " + size + " bytes through Android native allocator");
}
long address = directBufferAddress(buffer);
if (address != 0L) {
DEALLOCATORS.put(address, new Deallocator(buffer, address));
}
Comment thread
riccardobl marked this conversation as resolved.
return buffer;
}
Comment thread
riccardobl marked this conversation as resolved.

private static void freeCollectedBuffers() {
for (;;) {
try {
Deallocator deallocator = (Deallocator) REFERENCE_QUEUE.remove();
deallocator.freeNow();
} catch (InterruptedException exception) {
Thread.currentThread().interrupt();
break;
} catch (Throwable throwable) {
LOGGER.log(Level.SEVERE, "Error deallocating direct buffer", throwable);
}
}
}
Comment thread
riccardobl marked this conversation as resolved.

private static final class Deallocator extends PhantomReference<ByteBuffer> {
private final long address;
private final AtomicBoolean freed = new AtomicBoolean(false);

private Deallocator(ByteBuffer referent, long address) {
super(referent, REFERENCE_QUEUE);
this.address = address;
}

private void freeNow() {
if (!freed.compareAndSet(false, true)) {
return;
}
DEALLOCATORS.remove(address, this);
clear();
releaseDirectByteBufferAddress(address);
}
}
Comment thread
riccardobl marked this conversation as resolved.

/**
* Releases the memory of a direct buffer using a buffer object reference.
* Releases the memory of a direct buffer using its native address.
*
* @param buffer the buffer reference to release its memory.
* @param address the native address to release
* @see AndroidNativeBufferAllocator#destroyDirectBuffer(Buffer)
*/
private native void releaseDirectByteBuffer(Buffer buffer);
private static native void releaseDirectByteBufferAddress(long address);

private static native long directBufferAddress(Buffer buffer);
Comment thread
riccardobl marked this conversation as resolved.

/**
* Creates a new direct byte buffer explicitly with a specific size.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
/**
* This class contains the reflection based way to remove DirectByteBuffers in
* java, allocation is done via ByteBuffer.allocateDirect
* @deprecated This class relies on internal APIs that are not accessible in Java 9+, and is not thread-safe for allocation bookkeeping. Use {@code com.jme3.util.SaferBufferAllocator} from the {@code jme3-saferallocator} module instead.
*/
@Deprecated
public final class ReflectionAllocator implements BufferAllocator {
private static Method cleanerMethod = null;
private static Method cleanMethod = null;
Expand Down
1 change: 1 addition & 0 deletions jme3-ios-examples/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ sourceSets {
dependencies {
implementation project(':jme3-core')
implementation project(':jme3-ios')
implementation project(':jme3-saferallocator')
implementation libs.libjglios.core.ios
implementation libs.libjglios.gles.ios
implementation libs.libjglios.sdl3.ios
Expand Down
16 changes: 15 additions & 1 deletion jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public class IGLESContext implements JmeContext {

private static final String BLIT_MATERIAL = "Common/MatDefs/Blit/Blit.j3md";
private static final Logger logger = Logger.getLogger(IGLESContext.class.getName());
private static final String SAFER_BUFFER_ALLOCATOR_CLASS = "com.jme3.util.SaferBufferAllocator";
protected final AtomicBoolean created = new AtomicBoolean(false);
protected final AtomicBoolean renderable = new AtomicBoolean(false);
protected final AtomicBoolean needClose = new AtomicBoolean(false);
Expand Down Expand Up @@ -102,7 +103,20 @@ public class IGLESContext implements JmeContext {
final String implementation = BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION;

if (System.getProperty(implementation) == null) {
System.setProperty(implementation, LibJGLIOSNativeBufferAllocator.class.getName());
if (isClassPresent(SAFER_BUFFER_ALLOCATOR_CLASS)) {
System.setProperty(implementation, SAFER_BUFFER_ALLOCATOR_CLASS);
} else {
System.setProperty(implementation, LibJGLIOSNativeBufferAllocator.class.getName());
}
}
}

private static boolean isClassPresent(String className) {
try {
Class.forName(className, false, IGLESContext.class.getClassLoader());
return true;
} catch (Throwable ignored) {
return false;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,43 @@
*/
package com.jme3.util;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.ngengine.libjglios.core.LibJGLIOSBufferAllocator;


public final class LibJGLIOSNativeBufferAllocator implements BufferAllocator {
private static final Logger LOGGER = Logger.getLogger(LibJGLIOSNativeBufferAllocator.class.getName());
private static final ReferenceQueue<ByteBuffer> REFERENCE_QUEUE = new ReferenceQueue<>();
private static final Map<Long, Deallocator> DEALLOCATORS = new ConcurrentHashMap<>();
private static final Thread CLEAN_THREAD = new Thread(LibJGLIOSNativeBufferAllocator::freeCollectedBuffers);

static {
CLEAN_THREAD.setDaemon(true);
CLEAN_THREAD.setName("libJGLIOS Native Buffer Deallocator");
CLEAN_THREAD.start();
}

@Override
public void destroyDirectBuffer(Buffer toBeDestroyed) {
LibJGLIOSBufferAllocator.free(toBeDestroyed);
long address = LibJGLIOSBufferAllocator.baseAddress(toBeDestroyed);
if (address == 0L) {
LOGGER.log(Level.WARNING, "Not found address of the {0}", toBeDestroyed);
return;
}
Deallocator deallocator = DEALLOCATORS.remove(address);
if (deallocator == null) {
LOGGER.log(Level.WARNING, "Not found a deallocator for address {0}", address);
return;
}
deallocator.freeNow();
}

@Override
Expand All @@ -22,6 +49,43 @@ public ByteBuffer allocate(int size) {
if (buffer == null) {
throw new OutOfMemoryError("Could not allocate " + size + " bytes through libJGLIOS");
}
long address = LibJGLIOSBufferAllocator.baseAddress(buffer);
if (address != 0L) {
DEALLOCATORS.put(address, new Deallocator(buffer, address));
}
Comment thread
riccardobl marked this conversation as resolved.
return buffer;
}

private static void freeCollectedBuffers() {
for (;;) {
try {
Deallocator deallocator = (Deallocator) REFERENCE_QUEUE.remove();
deallocator.freeNow();
} catch (InterruptedException exception) {
Thread.currentThread().interrupt();
break;
} catch (Throwable throwable) {
LOGGER.log(Level.SEVERE, "Error deallocating direct buffer", throwable);
}
}
}
Comment thread
riccardobl marked this conversation as resolved.

private static final class Deallocator extends PhantomReference<ByteBuffer> {
private final long address;
private final AtomicBoolean freed = new AtomicBoolean(false);

private Deallocator(ByteBuffer referent, long address) {
super(referent, REFERENCE_QUEUE);
this.address = address;
}

private void freeNow() {
if (!freed.compareAndSet(false, true)) {
return;
}
DEALLOCATORS.remove(address, this);
clear();
LibJGLIOSBufferAllocator.freeAddress(address);
}
}
Comment thread
riccardobl marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
import com.jme3.util.BufferAllocatorFactory;
import com.jme3.util.BufferUtils;
import com.jme3.util.LWJGLBufferAllocator;
import com.jme3.util.LWJGLBufferAllocator.ConcurrentLWJGLBufferAllocator;
import com.jme3.util.LWJGLSaferAllocMemoryAllocator;

import java.nio.IntBuffer;
Expand Down Expand Up @@ -83,6 +82,8 @@ public abstract class LwjglContext implements JmeContext {
protected boolean useAngle = false;

private static final Logger logger = Logger.getLogger(LwjglContext.class.getName());
private static final String CONCURRENT_LWJGL_BUFFER_ALLOCATOR_CLASS =
"com.jme3.util.LWJGLBufferAllocator$ConcurrentLWJGLBufferAllocator";

static {
final String implementation = BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION;
Expand All @@ -95,8 +96,8 @@ public abstract class LwjglContext implements JmeContext {
LWJGLSaferAllocMemoryAllocator.SAFER_BUFFER_ALLOCATOR_CLASS);
}
} else if (configuredImplementation == null) {
if (Boolean.parseBoolean(System.getProperty(PROPERTY_CONCURRENT_BUFFER_ALLOCATOR, "true"))) {
System.setProperty(implementation, ConcurrentLWJGLBufferAllocator.class.getName());
if (Boolean.parseBoolean(System.getProperty(PROPERTY_CONCURRENT_BUFFER_ALLOCATOR, "false"))) {
System.setProperty(implementation, CONCURRENT_LWJGL_BUFFER_ALLOCATOR_CLASS);
} else {
System.setProperty(implementation, LWJGLBufferAllocator.class.getName());
}
Expand Down
Loading
Loading