// Copyright (c) FIRST and other WPILib contributors. // Open Source Software; you can modify and/or share it under the terms of // the WPILib BSD license file in the root directory of this project. #include "SimDeviceDataJNI.hpp" #include #include #include #include #include #include #include "SimulatorJNI.hpp" #include "org_wpilib_hardware_hal_simulation_SimDeviceDataJNI.h" #include "wpi/hal/handles/UnlimitedHandleResource.hpp" #include "wpi/hal/simulation/SimDeviceData.h" #include "wpi/util/jni_util.hpp" using namespace wpi::hal; using namespace wpi::util::java; static JClass simDeviceInfoCls; static JClass simValueInfoCls; static JClass simDeviceCallbackCls; static JClass simValueCallbackCls; static jmethodID simDeviceCallbackCallback; static jmethodID simValueCallbackCallback; namespace { struct DeviceInfo { DeviceInfo(const char* name_, HAL_SimDeviceHandle handle_) : name{name_}, handle{handle_} {} std::string name; HAL_SimValueHandle handle; jobject MakeJava(JNIEnv* env) const; }; struct ValueInfo { ValueInfo(const char* name_, HAL_SimValueHandle handle_, int32_t direction_, const HAL_Value& value_) : name{name_}, handle{handle_}, direction{direction_}, value{value_} {} std::string name; HAL_SimValueHandle handle; int32_t direction; HAL_Value value; jobject MakeJava(JNIEnv* env) const; }; } // namespace jobject DeviceInfo::MakeJava(JNIEnv* env) const { static jmethodID func = env->GetMethodID(simDeviceInfoCls, "", "(Ljava/lang/String;I)V"); return env->NewObject(simDeviceInfoCls, func, MakeJString(env, name), static_cast(handle)); } static std::pair ToValue12(const HAL_Value& value) { jlong value1 = 0; jdouble value2 = 0.0; switch (value.type) { case HAL_BOOLEAN: value1 = value.data.v_boolean; break; case HAL_DOUBLE: value2 = value.data.v_double; break; case HAL_ENUM: value1 = value.data.v_enum; break; case HAL_INT: value1 = value.data.v_int; break; case HAL_LONG: value1 = value.data.v_long; break; default: break; } return std::pair(value1, value2); } jobject ValueInfo::MakeJava(JNIEnv* env) const { static jmethodID func = env->GetMethodID(simValueInfoCls, "", "(Ljava/lang/String;IIIJD)V"); auto [value1, value2] = ToValue12(value); return env->NewObject(simValueInfoCls, func, MakeJString(env, name), static_cast(handle), static_cast(direction), static_cast(value.type), value1, value2); } namespace { class DeviceCallbackStore { public: void create(JNIEnv* env, jobject obj) { m_call = JGlobal(env, obj); } void performCallback(const char* name, HAL_SimDeviceHandle handle); void free(JNIEnv* env) { m_call.free(env); } void setCallbackId(int32_t id) { callbackId = id; } int32_t getCallbackId() { return callbackId; } private: wpi::util::java::JGlobal m_call; int32_t callbackId; }; class ValueCallbackStore { public: void create(JNIEnv* env, jobject obj) { m_call = JGlobal(env, obj); } void performCallback(const char* name, HAL_SimValueHandle handle, int32_t direction, const HAL_Value& value); void free(JNIEnv* env) { m_call.free(env); } void setCallbackId(int32_t id) { m_callbackId = id; } int32_t getCallbackId() { return m_callbackId; } private: wpi::util::java::JGlobal m_call; int32_t m_callbackId; }; } // namespace void DeviceCallbackStore::performCallback(const char* name, HAL_SimDeviceHandle handle) { JNIEnv* env; JavaVM* vm = sim::GetJVM(); bool didAttachThread = false; int tryGetEnv = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); if (tryGetEnv == JNI_EDETACHED) { // Thread not attached didAttachThread = true; if (vm->AttachCurrentThread(reinterpret_cast(&env), nullptr) != 0) { // Failed to attach, log and return std::puts("Failed to attach"); std::fflush(stdout); return; } } else if (tryGetEnv == JNI_EVERSION) { std::puts("Invalid JVM Version requested"); std::fflush(stdout); } env->CallVoidMethod(m_call, simDeviceCallbackCallback, MakeJString(env, name), static_cast(handle)); if (env->ExceptionCheck()) { env->ExceptionDescribe(); } if (didAttachThread) { vm->DetachCurrentThread(); } } void ValueCallbackStore::performCallback(const char* name, HAL_SimValueHandle handle, int32_t direction, const HAL_Value& value) { JNIEnv* env; JavaVM* vm = sim::GetJVM(); bool didAttachThread = false; int tryGetEnv = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); if (tryGetEnv == JNI_EDETACHED) { // Thread not attached didAttachThread = true; if (vm->AttachCurrentThread(reinterpret_cast(&env), nullptr) != 0) { // Failed to attach, log and return std::puts("Failed to attach"); std::fflush(stdout); return; } } else if (tryGetEnv == JNI_EVERSION) { std::puts("Invalid JVM Version requested"); std::fflush(stdout); } auto [value1, value2] = ToValue12(value); env->CallVoidMethod(m_call, simValueCallbackCallback, MakeJString(env, name), static_cast(handle), static_cast(direction), static_cast(value.type), value1, value2); if (env->ExceptionCheck()) { env->ExceptionDescribe(); } if (didAttachThread) { vm->DetachCurrentThread(); } } static wpi::hal::UnlimitedHandleResource< SIM_JniHandle, DeviceCallbackStore, wpi::hal::HAL_HandleEnum::SIMULATION_JNI>* deviceCallbackHandles; namespace { using RegisterDeviceCallbackFunc = int32_t (*)(const char* prefix, void* param, HALSIM_SimDeviceCallback callback, HAL_Bool initialNotify); using FreeDeviceCallbackFunc = void (*)(int32_t uid); } // namespace static SIM_JniHandle AllocateDeviceCallback( JNIEnv* env, const char* prefix, jobject callback, jboolean initialNotify, RegisterDeviceCallbackFunc createCallback) { auto callbackStore = std::make_shared(); auto handle = deviceCallbackHandles->Allocate(callbackStore); if (handle == HAL_kInvalidHandle) { return -1; } uintptr_t handleAsPtr = static_cast(handle); void* handleAsVoidPtr = reinterpret_cast(handleAsPtr); callbackStore->create(env, callback); auto callbackFunc = [](const char* name, void* param, HAL_SimDeviceHandle handle) { uintptr_t handleTmp = reinterpret_cast(param); SIM_JniHandle jnihandle = static_cast(handleTmp); auto data = deviceCallbackHandles->Get(jnihandle); if (!data) { return; } data->performCallback(name, handle); }; auto id = createCallback(prefix, handleAsVoidPtr, callbackFunc, initialNotify); callbackStore->setCallbackId(id); return handle; } static void FreeDeviceCallback(JNIEnv* env, SIM_JniHandle handle, FreeDeviceCallbackFunc freeCallback) { auto callback = deviceCallbackHandles->Free(handle); if (callback == nullptr) { return; } freeCallback(callback->getCallbackId()); callback->free(env); } static wpi::hal::UnlimitedHandleResource< SIM_JniHandle, ValueCallbackStore, wpi::hal::HAL_HandleEnum::SIMULATION_JNI>* valueCallbackHandles; namespace { using FreeValueCallbackFunc = void (*)(int32_t uid); } // namespace template static SIM_JniHandle AllocateValueCallback( JNIEnv* env, THandle h, jobject callback, jboolean initialNotify, int32_t (*createCallback)(THandle handle, void* param, HALSIM_SimValueCallback callback, HAL_Bool initialNotify)) { auto callbackStore = std::make_shared(); auto handle = valueCallbackHandles->Allocate(callbackStore); if (handle == HAL_kInvalidHandle) { return -1; } uintptr_t handleAsPtr = static_cast(handle); void* handleAsVoidPtr = reinterpret_cast(handleAsPtr); callbackStore->create(env, callback); auto callbackFunc = [](const char* name, void* param, HAL_SimValueHandle handle, int32_t direction, const HAL_Value* value) { uintptr_t handleTmp = reinterpret_cast(param); SIM_JniHandle jnihandle = static_cast(handleTmp); auto data = valueCallbackHandles->Get(jnihandle); if (!data) { return; } data->performCallback(name, handle, direction, *value); }; auto id = createCallback(h, handleAsVoidPtr, callbackFunc, initialNotify); callbackStore->setCallbackId(id); return handle; } static void FreeValueCallback(JNIEnv* env, SIM_JniHandle handle, FreeValueCallbackFunc freeCallback) { auto callback = valueCallbackHandles->Free(handle); if (callback == nullptr) { return; } freeCallback(callback->getCallbackId()); callback->free(env); } namespace wpi::hal::sim { bool InitializeSimDeviceDataJNI(JNIEnv* env) { simDeviceInfoCls = JClass( env, "org/wpilib/hardware/hal/simulation/SimDeviceDataJNI$SimDeviceInfo"); if (!simDeviceInfoCls) { return false; } simValueInfoCls = JClass( env, "org/wpilib/hardware/hal/simulation/SimDeviceDataJNI$SimValueInfo"); if (!simValueInfoCls) { return false; } simDeviceCallbackCls = JClass(env, "org/wpilib/hardware/hal/simulation/SimDeviceCallback"); if (!simDeviceCallbackCls) { return false; } simDeviceCallbackCallback = env->GetMethodID(simDeviceCallbackCls, "callback", "(Ljava/lang/String;I)V"); if (!simDeviceCallbackCallback) { return false; } simValueCallbackCls = JClass(env, "org/wpilib/hardware/hal/simulation/SimValueCallback"); if (!simValueCallbackCls) { return false; } simValueCallbackCallback = env->GetMethodID( simValueCallbackCls, "callbackNative", "(Ljava/lang/String;IIIJD)V"); if (!simValueCallbackCallback) { return false; } static wpi::hal::UnlimitedHandleResource< SIM_JniHandle, DeviceCallbackStore, wpi::hal::HAL_HandleEnum::SIMULATION_JNI> cbDevice; deviceCallbackHandles = &cbDevice; static wpi::hal::UnlimitedHandleResource< SIM_JniHandle, ValueCallbackStore, wpi::hal::HAL_HandleEnum::SIMULATION_JNI> cbValue; valueCallbackHandles = &cbValue; return true; } void FreeSimDeviceDataJNI(JNIEnv* env) { simDeviceInfoCls.free(env); simValueInfoCls.free(env); simDeviceCallbackCls.free(env); simValueCallbackCls.free(env); } } // namespace wpi::hal::sim extern "C" { /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: setSimDeviceEnabled * Signature: (Ljava/lang/String;Z)V */ JNIEXPORT void JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_setSimDeviceEnabled (JNIEnv* env, jclass, jstring prefix, jboolean enabled) { HALSIM_SetSimDeviceEnabled(JStringRef{env, prefix}.c_str(), enabled); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: isSimDeviceEnabled * Signature: (Ljava/lang/String;)Z */ JNIEXPORT jboolean JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_isSimDeviceEnabled (JNIEnv* env, jclass, jstring name) { return HALSIM_IsSimDeviceEnabled(JStringRef{env, name}.c_str()); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: registerSimDeviceCreatedCallback * Signature: (Ljava/lang/String;Ljava/lang/Object;Z)I */ JNIEXPORT jint JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_registerSimDeviceCreatedCallback (JNIEnv* env, jclass, jstring prefix, jobject callback, jboolean initialNotify) { return AllocateDeviceCallback(env, JStringRef{env, prefix}.c_str(), callback, initialNotify, &HALSIM_RegisterSimDeviceCreatedCallback); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: cancelSimDeviceCreatedCallback * Signature: (I)V */ JNIEXPORT void JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_cancelSimDeviceCreatedCallback (JNIEnv* env, jclass, jint uid) { FreeDeviceCallback(env, uid, &HALSIM_CancelSimDeviceCreatedCallback); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: registerSimDeviceFreedCallback * Signature: (Ljava/lang/String;Ljava/lang/Object;Z)I */ JNIEXPORT jint JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_registerSimDeviceFreedCallback (JNIEnv* env, jclass, jstring prefix, jobject callback, jboolean initialNotify) { return AllocateDeviceCallback(env, JStringRef{env, prefix}.c_str(), callback, initialNotify, &HALSIM_RegisterSimDeviceFreedCallback); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: cancelSimDeviceFreedCallback * Signature: (I)V */ JNIEXPORT void JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_cancelSimDeviceFreedCallback (JNIEnv* env, jclass, jint uid) { FreeDeviceCallback(env, uid, &HALSIM_CancelSimDeviceFreedCallback); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: getSimDeviceHandle * Signature: (Ljava/lang/String;)I */ JNIEXPORT jint JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_getSimDeviceHandle (JNIEnv* env, jclass, jstring name) { return HALSIM_GetSimDeviceHandle(JStringRef{env, name}.c_str()); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: getSimDeviceName * Signature: (I)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_getSimDeviceName (JNIEnv* env, jclass, jint handle) { const char* name = HALSIM_GetSimDeviceName(handle); if (!name) { return nullptr; } return MakeJString(env, name); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: getSimValueDeviceHandle * Signature: (I)I */ JNIEXPORT jint JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_getSimValueDeviceHandle (JNIEnv*, jclass, jint handle) { return HALSIM_GetSimValueDeviceHandle(handle); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: enumerateSimDevices * Signature: (Ljava/lang/String;)[Ljava/lang/Object; */ JNIEXPORT jobjectArray JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_enumerateSimDevices (JNIEnv* env, jclass, jstring prefix) { // get values std::vector arr; HALSIM_EnumerateSimDevices( JStringRef{env, prefix}.c_str(), &arr, [](const char* name, void* param, HAL_SimDeviceHandle handle) { auto arr = static_cast*>(param); arr->emplace_back(name, handle); }); // convert to java size_t numElems = arr.size(); jobjectArray jarr = env->NewObjectArray(arr.size(), simDeviceInfoCls, nullptr); if (!jarr) { return nullptr; } for (size_t i = 0; i < numElems; ++i) { JLocal elem{env, arr[i].MakeJava(env)}; env->SetObjectArrayElement(jarr, i, elem.obj()); } return jarr; } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: registerSimValueCreatedCallback * Signature: (ILjava/lang/Object;Z)I */ JNIEXPORT jint JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_registerSimValueCreatedCallback (JNIEnv* env, jclass, jint device, jobject callback, jboolean initialNotify) { return AllocateValueCallback(env, static_cast(device), callback, initialNotify, &HALSIM_RegisterSimValueCreatedCallback); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: cancelSimValueCreatedCallback * Signature: (I)V */ JNIEXPORT void JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_cancelSimValueCreatedCallback (JNIEnv* env, jclass, jint uid) { FreeValueCallback(env, uid, &HALSIM_CancelSimValueCreatedCallback); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: registerSimValueChangedCallback * Signature: (ILjava/lang/Object;Z)I */ JNIEXPORT jint JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_registerSimValueChangedCallback (JNIEnv* env, jclass, jint handle, jobject callback, jboolean initialNotify) { return AllocateValueCallback(env, static_cast(handle), callback, initialNotify, &HALSIM_RegisterSimValueChangedCallback); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: cancelSimValueChangedCallback * Signature: (I)V */ JNIEXPORT void JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_cancelSimValueChangedCallback (JNIEnv* env, jclass, jint uid) { FreeValueCallback(env, uid, &HALSIM_CancelSimValueChangedCallback); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: registerSimValueResetCallback * Signature: (ILjava/lang/Object;Z)I */ JNIEXPORT jint JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_registerSimValueResetCallback (JNIEnv* env, jclass, jint handle, jobject callback, jboolean initialNotify) { return AllocateValueCallback(env, static_cast(handle), callback, initialNotify, &HALSIM_RegisterSimValueResetCallback); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: cancelSimValueResetCallback * Signature: (I)V */ JNIEXPORT void JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_cancelSimValueResetCallback (JNIEnv* env, jclass, jint uid) { FreeValueCallback(env, uid, &HALSIM_CancelSimValueResetCallback); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: getSimValueHandle * Signature: (ILjava/lang/String;)I */ JNIEXPORT jint JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_getSimValueHandle (JNIEnv* env, jclass, jint device, jstring name) { return HALSIM_GetSimValueHandle(device, JStringRef{env, name}.c_str()); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: enumerateSimValues * Signature: (I)[Ljava/lang/Object; */ JNIEXPORT jobjectArray JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_enumerateSimValues (JNIEnv* env, jclass, jint device) { // get values std::vector arr; HALSIM_EnumerateSimValues( device, &arr, [](const char* name, void* param, HAL_SimValueHandle handle, HAL_Bool readonly, const HAL_Value* value) { auto arr = static_cast*>(param); arr->emplace_back(name, handle, readonly, *value); }); // convert to java size_t numElems = arr.size(); jobjectArray jarr = env->NewObjectArray(arr.size(), simValueInfoCls, nullptr); if (!jarr) { return nullptr; } for (size_t i = 0; i < numElems; ++i) { JLocal elem{env, arr[i].MakeJava(env)}; env->SetObjectArrayElement(jarr, i, elem.obj()); } return jarr; } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: getSimValueEnumOptions * Signature: (I)[Ljava/lang/Object; */ JNIEXPORT jobjectArray JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_getSimValueEnumOptions (JNIEnv* env, jclass, jint handle) { static JClass stringCls{env, "java/lang/String"}; if (!stringCls) { return nullptr; } int32_t numElems = 0; const char** elems = HALSIM_GetSimValueEnumOptions(handle, &numElems); jobjectArray jarr = env->NewObjectArray(numElems, stringCls, nullptr); if (!jarr) { return nullptr; } for (int32_t i = 0; i < numElems; ++i) { JLocal elem{env, MakeJString(env, elems[i])}; env->SetObjectArrayElement(jarr, i, elem.obj()); } return jarr; } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: getSimValueEnumDoubleValues * Signature: (I)[D */ JNIEXPORT jdoubleArray JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_getSimValueEnumDoubleValues (JNIEnv* env, jclass, jint handle) { int32_t numElems = 0; const double* elems = HALSIM_GetSimValueEnumDoubleValues(handle, &numElems); return MakeJDoubleArray(env, std::span(elems, numElems)); } /* * Class: org_wpilib_hardware_hal_simulation_SimDeviceDataJNI * Method: resetSimDeviceData * Signature: ()V */ JNIEXPORT void JNICALL Java_org_wpilib_hardware_hal_simulation_SimDeviceDataJNI_resetSimDeviceData (JNIEnv*, jclass) { HALSIM_ResetSimDeviceData(); } } // extern "C"