You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
135 lines
5.4 KiB
C#
135 lines
5.4 KiB
C#
3 months ago
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||
|
|
||
|
#if (UNITY_ANDROID && !UNITY_EDITOR)
|
||
|
|
||
|
using System;
|
||
|
using UnityEngine;
|
||
|
|
||
|
namespace Lofelt.NiceVibrations
|
||
|
{
|
||
|
// Android JNI call wrappers that are more efficient than AndroidJavaObject::Call()
|
||
|
//
|
||
|
// Calling a method via AndroidJavaObject, e.g. `lofeltHaptics.Call("play")`, is inefficient:
|
||
|
// - It looks up the method by name for each call
|
||
|
// - It allocates memory during method lookup and argument conversion
|
||
|
//
|
||
|
// JNIHelpers provides alternative Call() methods that are more efficient:
|
||
|
// - It allows calling by method ID rather by method name, so that the method only needs to
|
||
|
// be looked up once, not for every call
|
||
|
// - It does not allocate memory for converting the arguments to jvalue[]
|
||
|
//
|
||
|
// In addition to that, exceptions thrown in Java are handled automatically by logging them.
|
||
|
//
|
||
|
// The Call() overload here do not cover all cases that AndroidJavaObject::Call() covers. For
|
||
|
// example, only methods with one argument are supported, and that only for certain types. In
|
||
|
// addition, not all overloads are free of allocations. This however is good enough so that the
|
||
|
// calls triggered by common playback scenarios such as HapticController::Play() and
|
||
|
// HapticPatterns::PlayPreset() don't allocate.
|
||
|
internal static class JNIHelpers
|
||
|
{
|
||
|
// The array for the JNI arguments is created here, so that it doesn't need to be created
|
||
|
// for every call. This saves the allocation in each call.
|
||
|
// The array supports only methods with 0 or 1 argument, but that covers our needs.
|
||
|
static jvalue[] jniArgs = new jvalue[1];
|
||
|
|
||
|
// Returns an exception message and stack trace for the given Java exception
|
||
|
static String javaThrowableToString(IntPtr throwable)
|
||
|
{
|
||
|
IntPtr throwableClass = AndroidJNI.FindClass("java/lang/Throwable");
|
||
|
IntPtr androidUtilLogClass = AndroidJNI.FindClass("android/util/Log");
|
||
|
try
|
||
|
{
|
||
|
IntPtr toStringMethodId = AndroidJNI.GetMethodID(throwableClass, "toString", "()Ljava/lang/String;");
|
||
|
IntPtr getStackTraceStringMethodId = AndroidJNI.GetStaticMethodID(androidUtilLogClass, "getStackTraceString", "(Ljava/lang/Throwable;)Ljava/lang/String;");
|
||
|
string exceptionMessage = AndroidJNI.CallStringMethod(throwable, toStringMethodId, new jvalue[] { });
|
||
|
jniArgs[0].l = throwable;
|
||
|
string exceptionCallStack = AndroidJNI.CallStaticStringMethod(androidUtilLogClass, getStackTraceStringMethodId, jniArgs);
|
||
|
return exceptionMessage + "\n" + exceptionCallStack;
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
if (throwable != IntPtr.Zero)
|
||
|
AndroidJNI.DeleteLocalRef(throwable);
|
||
|
if (throwableClass != IntPtr.Zero)
|
||
|
AndroidJNI.DeleteLocalRef(throwableClass);
|
||
|
if (androidUtilLogClass != IntPtr.Zero)
|
||
|
AndroidJNI.DeleteLocalRef(androidUtilLogClass);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static void Call(AndroidJavaObject obj, IntPtr methodId, jvalue[] jniArgs)
|
||
|
{
|
||
|
if (methodId == IntPtr.Zero)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
AndroidJNI.CallVoidMethod(obj.GetRawObject(), methodId, jniArgs);
|
||
|
IntPtr throwable = AndroidJNI.ExceptionOccurred();
|
||
|
if (throwable != IntPtr.Zero)
|
||
|
{
|
||
|
AndroidJNI.ExceptionClear();
|
||
|
String exception = javaThrowableToString(throwable);
|
||
|
Debug.LogError(exception);
|
||
|
}
|
||
|
}
|
||
|
catch (Exception ex)
|
||
|
{
|
||
|
Debug.LogException(ex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static void Call(AndroidJavaObject obj, IntPtr methodId)
|
||
|
{
|
||
|
jniArgs[0].l = System.IntPtr.Zero;
|
||
|
Call(obj, methodId, jniArgs);
|
||
|
}
|
||
|
|
||
|
public static void Call(AndroidJavaObject obj, IntPtr methodId, float arg)
|
||
|
{
|
||
|
jniArgs[0].f = arg;
|
||
|
Call(obj, methodId, jniArgs);
|
||
|
}
|
||
|
|
||
|
public static void Call(AndroidJavaObject obj, IntPtr methodId, bool arg)
|
||
|
{
|
||
|
jniArgs[0].z = arg;
|
||
|
Call(obj, methodId, jniArgs);
|
||
|
}
|
||
|
|
||
|
public static void Call(AndroidJavaObject obj, IntPtr methodId, float[] arg)
|
||
|
{
|
||
|
// The allocations in the next two lines could probably be removed to optimize this
|
||
|
// further.
|
||
|
object[] args = new object[] { arg };
|
||
|
jvalue[] jniArgs = AndroidJNIHelper.CreateJNIArgArray(args);
|
||
|
try
|
||
|
{
|
||
|
JNIHelpers.Call(obj, methodId, jniArgs);
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
AndroidJNIHelper.DeleteJNIArgArray(args, jniArgs);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The method isn't yet optimized to reduce allocations, but unlike the other overloads of
|
||
|
// Call(), it supports non-void return types.
|
||
|
public static ReturnType Call<ReturnType>(AndroidJavaObject obj, string methodName)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
return obj.Call<ReturnType>(methodName);
|
||
|
}
|
||
|
catch (Exception ex)
|
||
|
{
|
||
|
Debug.LogException(ex);
|
||
|
return default(ReturnType);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
#endif
|