Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
jni.hpp
Go to the documentation of this file.
1#pragma once
2
3// pulp::android — JNI bridge utilities
4//
5// RAII wrappers for JNI references, thread-safe JNIEnv access,
6// and class caching infrastructure. All com.pulp.* class lookups
7// happen in JNI_OnLoad (main thread, app ClassLoader) and are cached
8// as Global References for use by native-spawned threads.
9
10#if defined(__ANDROID__)
11
12#include <jni.h>
13#include <cassert>
14#include <atomic>
15#include <string>
16#include <vector>
17
18namespace pulp::android {
19
20// ── Global JVM Reference ──────────────────────────────────────────────────
21// Set in JNI_OnLoad, never changes. Safe to read from any thread.
22inline JavaVM* g_vm = nullptr;
23
24// ── APK Asset Reader ──────────────────────────────────────────────────────
25// Reads bundled assets from the APK via AAssetManager.
26// Must be initialized from Kotlin via PulpFileProvider.init() before use.
27// Thread-safe: AAssetManager is safe to use from any thread.
28
29void init_asset_manager(JNIEnv* env, jobject context);
30std::vector<uint8_t> read_asset(const std::string& path);
31std::string read_asset_text(const std::string& path);
32bool has_asset_manager();
33
34// ── Get JNIEnv for Current Thread ─────────────────────────────────────────
35// Auto-attaches non-Java threads via AttachCurrentThread.
36// NEVER cache the returned pointer across threads.
37inline JNIEnv* get_env() {
38 assert(g_vm && "JNI_OnLoad not called — System.loadLibrary missing?");
39 JNIEnv* env = nullptr;
40 auto result = g_vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
41 if (result == JNI_EDETACHED) {
42 g_vm->AttachCurrentThread(&env, nullptr);
43 }
44 return env;
45}
46
47// ── RAII Local Reference ──────────────────────────────────────────────────
48// Wraps a JNI local reference that is deleted on scope exit.
49template<typename T>
50class LocalRef {
51 JNIEnv* env_;
52 T ref_;
53public:
54 LocalRef(JNIEnv* env, T ref) : env_(env), ref_(ref) {}
55 ~LocalRef() { if (ref_) env_->DeleteLocalRef(ref_); }
56
57 LocalRef(const LocalRef&) = delete;
58 LocalRef& operator=(const LocalRef&) = delete;
59 LocalRef(LocalRef&& other) noexcept : env_(other.env_), ref_(other.ref_) { other.ref_ = nullptr; }
60 LocalRef& operator=(LocalRef&& other) noexcept {
61 if (this != &other) {
62 if (ref_) env_->DeleteLocalRef(ref_);
63 env_ = other.env_;
64 ref_ = other.ref_;
65 other.ref_ = nullptr;
66 }
67 return *this;
68 }
69
70 T get() const { return ref_; }
71 explicit operator bool() const { return ref_ != nullptr; }
72 T release() { T r = ref_; ref_ = nullptr; return r; }
73};
74
75// ── RAII Global Reference ─────────────────────────────────────────────────
76// For Kotlin/Java objects that must outlive a single JNI call.
77// Thread-safe: the reference itself is valid from any thread.
78//
79// WARNING: Android has a hard limit on Global References (~51,200).
80// In debug builds, we track the count and assert if it gets too high.
81class GlobalRef {
82 jobject ref_ = nullptr;
83
84#ifndef NDEBUG
85 static inline std::atomic<int> g_count{0};
86#endif
87
88public:
89 GlobalRef() = default;
90
91 GlobalRef(JNIEnv* env, jobject local) {
92 if (local) {
93 ref_ = env->NewGlobalRef(local);
94#ifndef NDEBUG
95 int count = g_count.fetch_add(1) + 1;
96 assert(count < 1000 && "Possible JNI Global Reference leak — over 1000 active refs");
97#endif
98 }
99 }
100
101 ~GlobalRef() {
102 if (ref_) {
103 get_env()->DeleteGlobalRef(ref_);
104#ifndef NDEBUG
105 g_count.fetch_sub(1);
106#endif
107 }
108 }
109
110 GlobalRef(const GlobalRef&) = delete;
111 GlobalRef& operator=(const GlobalRef&) = delete;
112 GlobalRef(GlobalRef&& other) noexcept : ref_(other.ref_) { other.ref_ = nullptr; }
113 GlobalRef& operator=(GlobalRef&& other) noexcept {
114 if (this != &other) {
115 if (ref_) {
116 get_env()->DeleteGlobalRef(ref_);
117#ifndef NDEBUG
118 g_count.fetch_sub(1);
119#endif
120 }
121 ref_ = other.ref_;
122 other.ref_ = nullptr;
123 }
124 return *this;
125 }
126
127 jobject get() const { return ref_; }
128 explicit operator bool() const { return ref_ != nullptr; }
129
130#ifndef NDEBUG
131 static int active_count() { return g_count.load(); }
132#endif
133};
134
135// ── RAII Float Array Pin ──────────────────────────────────────────────────
136// Pins a jfloatArray for native access, releases on destruction.
137class FloatArrayPin {
138 JNIEnv* env_;
139 jfloatArray array_;
140 jfloat* data_;
141public:
142 FloatArrayPin(JNIEnv* env, jfloatArray array)
143 : env_(env), array_(array)
144 , data_(env->GetFloatArrayElements(array, nullptr)) {}
145
146 ~FloatArrayPin() {
147 if (data_) env_->ReleaseFloatArrayElements(array_, data_, 0);
148 }
149
150 FloatArrayPin(const FloatArrayPin&) = delete;
151 FloatArrayPin& operator=(const FloatArrayPin&) = delete;
152
153 jfloat* data() { return data_; }
154 const jfloat* data() const { return data_; }
155 explicit operator bool() const { return data_ != nullptr; }
156 jsize size() const { return env_->GetArrayLength(array_); }
157};
158
159// ── Cached Class References ───────────────────────────────────────────────
160// Looked up in JNI_OnLoad (main thread, app ClassLoader).
161// Safe to use from any thread — they are Global References.
162namespace classes {
163 inline jclass PulpBridge = nullptr;
164 inline jclass PulpAudioEngine = nullptr;
165 inline jclass PulpMidiManager = nullptr;
166 inline jclass PulpFileProvider = nullptr;
167 inline jclass PulpActivity = nullptr;
168}
169
170// Cache a class as a Global Reference. Call only from JNI_OnLoad.
171inline jclass cache_class(JNIEnv* env, const char* name) {
172 jclass local = env->FindClass(name);
173 if (!local) {
174 // Fatal: class not found. App is misconfigured.
175 // Can't use android logging here without <android/log.h> — just abort.
176 std::abort();
177 }
178 jclass global = static_cast<jclass>(env->NewGlobalRef(local));
179 env->DeleteLocalRef(local);
180 return global;
181}
182
183} // namespace pulp::android
184
185#endif // __ANDROID__
@ count
sentinel: number of real capabilities (keep LAST)
Definition canvas_capability.hpp:38
@ release
Release only: voices enter their normal release and tails ring out (MIDI CC 123 semantic).
Definition panic.hpp:39
std::optional< std::string > get_env(std::string_view name)
Definition system.hpp:14
@ other
Definition udev_monitor.hpp:26
@ size
Definition config.hpp:216
@ r
Definition input_events.hpp:41