Electrostat-Lab / jme-alloc

A direct dynamic memory allocation API for jMonkeyEngine lwjgl-2 and android games
https://hub.jmonkeyengine.org/t/jme-alloc-project/46356
BSD 3-Clause "New" or "Revised" License
6 stars 1 forks source link

[GC] Attach DirectBuffers to GC #66

Closed pavly-gerges closed 1 year ago

pavly-gerges commented 1 year ago

Example to utilizing the java.lang.ref.PhantomReference<T>.

Note: the following is a pseudo-code intended to show off the interface and the implementation design.

1) GarbageCollectibleBuffer: A GC PhantomReference wrapping a native buffer.

public class GarbageCollectibleBuffer extends PhantomReference<Buffer> {
      private final Buffer referent;
      private final long address;

      private GarbageCollectibleBuffer(final Buffer referent, final ReferenceQueue<? super Buffer> queue) {
              super(referent, queue);
              this.referent = referent;
              this.address = NativeBufferUtils.getMemoryAddress(buffer);
      }

      public static GarbageCollectibleBuffer from(final Buffer buffer, final ReferenceQueue<? super Buffer> queue) {
             if (!buffer.isDirect()) {
                throw new UnSupportedBufferException("Target Buffer isnot a direct Buffer!");
             }
             return new GarbageCollectibleBuffer(buffer, queue);
      }

      public static GarbageCollectibleBuffer allocateDirect(final long size, final ReferenceQueue<? super Buffer> queue) {
              final ByteBuffer buffer = NativeBufferUtils.clearAlloc(size);
              return GarbageCollectibleBuffer.from(buffer, queue);       
      }

      public void deallocate() {
            NativeBufferUtils.destroy(referent);
      }

      public Buffer getReferent() {
           return referent;     
      }

      public long getDirectBufferAddress() {
           return address;
      }
}

2) GarbageCollectableBuffers: A collection of direct collectible buffers.

public final class GarbageCollectibleBuffers {
      private static ReferenceQueue<Buffer> COLLECTIBLES = new ReferenceQueue<>();
      private final List<GarbageCollectableBuffer> buffers = new ArrayList<>();

      private GarbageCollectibleBuffers() {
      }

      public static GarbageCollectibleBuffer allocate(final long size) {
            final GarbageCollectibleBuffer collectible = GarbageCollectibleBuffer.allocateDirect(size, COLLECTIBLES);
            buffers.add(collectible);
            return collectible;
      }

      public static GarbageCollectibleBuffer register(final Buffer buffer) {
            if (!buffer.isDirect()) {
                   throw new UnSupportedBufferException("Buffer isn't a direct buffer!");
            }
            final GarbageCollectibleBuffer collectible = GarbageCollectibleBuffer.from(buffer, COLLECTIBLES);
            buffers.add(collectible);
            return collectible;
      }

      public static void deallocate(final Buffer buffer) {
            if (!buffers.contains(buffer)) { 
                  throw new UnSupportedBufferException("Buffer is not a direct collectible!");
            } 
            NativeBufferUtils.destroy(buffer);
            buffers.remove(collectible);
      }

      public static MemoryScavenger startMemoryScavenger() {
          return MemoryScavenger.start(COLLECTIBLES);
      }
}

3) MemoryScavenger: A memory cleaner to help GC scavenge the native memory.

public final class MemoryScavenger extends Thread {
       private final ReferenceQueue<? super Buffer> queue;

       private MemoryScavenger(final ReferenceQueue<? super Buffer> queue) {
            super(MemoryScavenger.class.getName());
            setDaemon(true);
            this.queue = queue;
       }

       public static MemoryScavenger start(final ReferenceQueue<? super Buffer> queue) { 
            final MemoryScavenger scavenger = new MemoryScavenger(queue);
            scavenger.start();
            return scavenger;
       }

       @Override
       public void run() {
            for (;;) {
                // blocks until an object is available in the queue before returning and removing it
                // object references are added to the queue by the GC as a part of post-mortem actions
                GarbageCollectibleBuffer collectible = (GarbageCollectibleBuffer) queue.remove();
                collectible.deallocate();
            }
       }
}

------------------------------------------------ JME ------------------------------------------------

4) Implementing the above interface in jme would be super-easy:

LwjglBufferAllocator: A direct buffer allocator/deallocator for LWJGL-2.

public class LwjglBufferAllocator implements BufferAllocator {
        static {
            GarbageCollectibleBuffers.startScavenger();
        }

        @Override
        public ByteBuffer allocate(final int size) {
            return GarbageCollectibleBuffer.register(size).getReferent();
        }

        @Override
        public void destroyDirectBuffer(final Buffer buffer) {
            NativeBufferAllocator.release(buffer);
        }
}

AndroidNativeBufferAllocator: A native buffer allocator/deallocator for android.

public class AndroidNativeBufferAllocator implements BufferAllocator {
        static {
            GarbageCollectibleBuffers.startScavenger();
        }

        @Override
        public ByteBuffer allocate(final int size) {
            return GarbageCollectibleBuffers.allocate(size).getReferent();
        }

        @Override
        public void destroyDirectBuffer(final Buffer buffer) {
             GarbageCollectibleBuffers.deallocate(buffer);
        }
}