Bootstrap y ciclo de vida¶
El paquete de Unity incluye un asistente estático, VisioForgeEnvironment, que pone en marcha el runtime nativo incluido antes de que cargue la primera escena. No lo invocas desde tus scripts: Unity lo llama automáticamente mediante RuntimeInitializeOnLoadMethod. Esta página explica qué hace en cada plataforma y las reglas de ciclo de vida que tus scripts deben respetar.
Si solo necesitas distribuir el SDK, puedes saltar esta página: arrastra MediaBlocksPlayer o RTSPViewerPlayer a una escena y pulsa Play. Vuelve aquí cuando escribas tus propios scripts, encuentres un error en tiempo de ejecución o quieras entender por qué los ajustes del Editor son obligatorios.
Los dos puntos de entrada¶
VisioForgeEnvironment tiene exactamente dos métodos públicos con los que tu código interactúa:
| Método | Cuándo se ejecuta | Qué hace |
|---|---|---|
Configure() | Automáticamente, antes de cargar la primera escena ([RuntimeInitializeOnLoadMethod(BeforeSceneLoad)]). | Prepara el runtime nativo para la plataforma actual — ruta de búsqueda DLL, variables de entorno, paquete CA, bootstrap Java. Idempotente. |
InitializeSdk() | Lo llamas una vez antes de construir un pipeline. Los players de ejemplo lo hacen en Start(). | Llama a VisioForgeX.InitSDK() y escanea el registro de plugins incluido. Idempotente. |
Ambos métodos son estáticos. Ambos marcan su finalización solo tras ejecutar con éxito cada paso — así un fallo recuperable (por ejemplo, el bootstrap Java de Android ejecutándose antes de que currentActivity esté lista) deja el flag sin marcar y una llamada posterior vuelve a intentarlo en lugar de fallar silenciosamente en un estado roto.
Qué hace Configure() en cada plataforma¶
Configure() despacha sobre símbolos de compilación (UNITY_STANDALONE_WIN, UNITY_STANDALONE_OSX, UNITY_ANDROID, UNITY_IOS) — Unity define exactamente uno por target de build. El cuerpo de cada rama es lo mínimo que la plataforma necesita para que GStreamer encuentre sus plugins, localice sus raíces TLS y resuelva el resto del runtime incluido a través de su cargador nativo.
- Valida que la carpeta de nativos incluida existe (
StreamingAssets/VisioForge/x64). Rechaza modificar cualquier estado del proceso si no existe — una carpeta ausente no debe envenenar elPATHdel proceso. - Elimina cualquier entrada de GStreamer del sistema del PATH del proceso (una instalación homebrew / sistema en
PATHcargaría una segunda copia física degstreamer-1.0-0.dll, registraría tipos GLib por duplicado y colgaría el pipeline). - Apunta el cargador DLL Win32 a los nativos incluidos mediante
SetDllDirectoryW. - Antepone la carpeta de nativos al
PATHdel proceso para que las dependencias core-lib transitivas de cada plugin se resuelvan (SetDllDirectorypor sí solo no basta para esas). - Establece
GST_PLUGIN_PATH/GST_PLUGIN_SYSTEM_PATHa la misma carpeta plana. - Establece
SSL_CERT_FILEyCA_CERTIFICATESalca-certificates.crtincluido para que RTSPS y HTTPS verifiquen pares.
El PATH del usuario / sistema nunca se toca — solo la copia viva del proceso.
- Resuelve la ruta de nativos sondeando los layouts conocidos de Unity:
Plugins/macOS(Editor y algunas versiones del Standalone Player), luegoContents/PlugIns,Contents/PlugIns/macOS,Contents/FrameworksyContents/Resources/Data/Plugins/macOSpara un Standalone.app. Gana la primera carpeta que contengalibgstreamer-1.0.0.dylib. El resultado se cachea. - Elimina cualquier GStreamer de sistema / homebrew de
DYLD_LIBRARY_PATH(/opt/homebrew/lib,/usr/local/lib) — el mismo modo de fallo de doble init que la limpieza delPATHde Windows. - Establece
GST_PLUGIN_PATH/GST_PLUGIN_SYSTEM_PATHa la carpeta de nativos para que GStreamer pueda enumerar los plugins incluidos. - Establece
GIO_MODULE_DIRpara que GIO encuentrelibgioopenssl.so(el backend TLS por el que RTSPS / HTTPS verifican pares). - Establece
SSL_CERT_FILEyCA_CERTIFICATESal paquete CA incluido.
Cada .dylib incluido tiene su @rpath / @loader_path precocido por el NuGet pack, así que una vez que dyld ha cargado uno mediante el primer [DllImport], los demás resuelven hermanos automáticamente — no se necesita equivalente de SetDllDirectory.
- Adquiere
com.unity3d.player.UnityPlayer.currentActivitymediante JNI. Si el campo es null — variantes Wear OS / Android TV, hosts Unity-as-a-library que aún no lo han asignado, paths de arranqueBeforeSceneLoadmuy tempranos en algunos Unity 6 — falla rápido con una excepción descriptiva para que el cliente vea algo mejor que unNullReferenceExceptionopaco desde el interior de JNI. - Resuelve
getFilesDir()ygetCacheDir()desde la Activity. - Captura los valores previos de
TMP,TEMP,TMPDIR,XDG_RUNTIME_DIR,XDG_CACHE_HOME,HOME,GST_REGISTRY,SSL_CERT_FILE,CA_CERTIFICATESpara que un fallo posterior pueda revertirlos. - Apunta GLib a los directorios escribibles privados de la app estableciendo las variables anteriores (solo los directorios privados de la app son escribibles en Android).
- Extrae el recurso embebido
ca-certificates.crtafilesDir/ssl/certs/y apuntaSSL_CERT_FILE/CA_CERTIFICATESallí. - Llama a
org.freedesktop.gstreamer.GStreamer.init(activity)— esto cargalibgstreamer_android.so, captura la JavaVM enJNI_OnLoad, ejecutagst_inity registra cada plugin enlazado estáticamente al monolito. - Si algo de lo anterior lanza, restaura el entorno capturado y relanza.
- Extrae el recurso embebido
ca-certificates.crtaApplication.persistentDataPath/ssl/certs/. - Establece
SSL_CERT_FILE/CA_CERTIFICATESa esa ruta para que el backend OpenSSL de GIO pueda verificar pares RTSPS / HTTPS. - Precarga la caché de
NativesPathdesde el hilo principal (s_cachedNativesPath = Application.dataPath.Replace('\\', '/')). Sin esta precarga, un lector en un hilo de fondo — el bus de GStreamer, un callback de log async, un callback pad-added — golpearía después al getter perezoso y llamaría aApplication.dataPathfuera del hilo principal, lo que lanzaUnityException. La fórmula de cálculo coincide con el getter perezoso byte a byte.
iOS no necesita un escaneo de plugins: cada plugin de GStreamer está registrado estáticamente dentro de GStreamerX.framework en el momento de gst_plugin_register_static(), y dyld resuelve @rpath/GStreamerX.framework/GStreamerX automáticamente cuando dispara el primer [DllImport].
Configure() establece el flag de éxito y registra una advertencia. No existe runtime nativo para la plataforma actual, así que cualquier [DllImport] posterior lanzaría DllNotFoundException. Compila para una de las cuatro plataformas soportadas.
Qué hace InitializeSdk()¶
Tras que Configure() haya preparado el runtime, InitializeSdk() termina la puesta en marcha:
- Rechaza ejecutarse si
Configure()nunca tuvo éxito — fallar rápido aquí saca un error accionable en lugar de unDllNotFoundExceptiondesde el interior del SDK. - Rechaza ejecutarse en un valor de
Application.platformno soportado en tiempo de ejecución (se admiten Windows / Android / macOS / iOS; cualquier otro provoca un cortocircuito con advertencia). - En Windows y macOS, antes de llamar al SDK nativo, vuelve a comprobar que la carpeta de nativos resuelta exista en disco. Esto detecta el desajuste de flavor cruzado (un
.unitypackagesolo-Windows importado en un host macOS, o lo contrario) con un mensaje claro en lugar de unDllNotFoundExceptionopaco. Android e iOS omiten esta comprobación (no hay carpeta que sondear). - Llama a
VisioForgeX.InitSDK(). Captura y registra en caso de fallo; deja el flag sin marcar para que un reintento posterior pueda tener éxito. - En Windows y macOS, escanea explícitamente la carpeta de plugins incluida con
Gst.Registry.Get().ScanPath(NativesPath). El escaneo in-process de plugins de Unity no honra fiablementeGST_PLUGIN_PATHen ninguna de las dos plataformas; el escaneo explícito es lo que hace que bloques comoBufferSinkBlock(que depende deappsink) se carguen. Android registra plugins estáticamente enGStreamer.init; iOS los registra estáticamente en el framework — ambos omiten el escaneo. - Establece el flag de éxito.
Los players de ejemplo (MediaBlocksPlayer, RTSPViewerPlayer) llaman a InitializeSdk() desde su método Start(), antes de construir un pipeline. Tus scripts deben seguir el mismo patrón.
El ciclo de vida del Editor¶
El SDK se inicializa una vez por proceso del Editor y se reusa a través de las sesiones Play → Stop → Play. Dos consecuencias:
- Disable Domain Reload es obligatorio. Con él habilitado, salir de modo Play dispara una recarga de dominio mientras el hilo del bucle principal GLib del SDK aún corre, lo que puede colgar el Editor. El diálogo de ajustes del Editor que el paquete muestra al primer import lo configura por ti; ajústalo manualmente en Edit → Project Settings → Editor → Enter Play Mode Settings si saltaste ese diálogo.
- No llames a
VisioForgeX.DestroySDK()en Stop ni enOnDestroy.gst_deinitde GStreamer no puede reinicializarse en el mismo proceso — destruir el SDK en Stop e intentar usarlo de nuevo en el siguiente Play crashea dentro del registro nativo. Los players de ejemplo siguen esta regla: suOnDestroysolo libera el pipeline por-Play. El SDK permanece vivo durante el resto del ciclo de vida del proceso.
Hay un guard Editor-only que el paquete instala automáticamente: un VisioForgeEditorReloadGuard que llama a VisioForgeX.StopMainLoop() en beforeAssemblyReload y EditorApplication.quitting. El bucle principal GLib corre en un hilo de fondo dedicado, bloqueado dentro de una llamada nativa que Unity no puede abortar — sin este guard, la recarga de dominio que sigue a una recompilación de script se colgaría. El guard no llama a DestroySDK (ver arriba); solo para el hilo del bucle, y el siguiente Play reconstruye el bucle. Este guard es interno — tus scripts deben ignorarlo.
Preguntas Frecuentes¶
¿Tengo que llamar a Configure() manualmente?¶
No. El atributo [RuntimeInitializeOnLoadMethod(BeforeSceneLoad)] de Unity lo ejecuta por ti antes de que cargue la primera escena. La única vez que lo volverías a llamar es desde un path de recuperación personalizado cuando un intento anterior falló — y Configure() es idempotente, así que una llamada redundante es inocua.
¿Por qué Configure() modifica variables de entorno en lugar de pasar argumentos?¶
GLib lee GST_PLUGIN_PATH, GIO_MODULE_DIR, SSL_CERT_FILE, HOME, etc. del environ C directamente durante gst_init y de nuevo en el primer uso TLS. El SDK no tiene una API para sobrescribirlos — la única forma correcta de apuntar el runtime a los assets incluidos es fijar las variables antes de construir cualquier pipeline. Las mutaciones se limitan al proceso; los entornos de usuario y sistema quedan intactos.
¿Qué pasa si llamo a InitializeSdk() antes de que Configure() haya tenido éxito?¶
Registra un error y retorna. El flag de éxito queda sin marcar para que un reintento posterior pueda tener éxito una vez que Configure() funcione. Este guard existe porque InitSDK() de otro modo crashearía dentro del código nativo con un error mucho menos accionable.
¿Puedo ejecutar dos pipelines en paralelo?¶
Sí. InitializeSdk() arranca el SDK una vez por proceso; después puedes construir tantas instancias de MediaBlocksPipeline como quieras. Cada una es independiente — el patrón de muestra multi-cámara RTSP consiste en adjuntar un RTSPViewerPlayer por RawImage, y cada uno construye y destruye su propio pipeline.
Véase también¶
- Uso de VisioForge en Unity — visión general del paquete y cómo funciona el rendering
- Compilar para Windows — ajustes del Editor y Standalone para Windows
- Compilar para Android — ajustes IL2CPP para Android
- Compilar para macOS — ajustes del Standalone para macOS
- Compilar para iOS — ajustes de dispositivo para iOS
- Solución de problemas — errores comunes de bootstrap y runtime