using System.Collections.Generic; using Rive.Utils; using UnityEngine; namespace Rive.Components.Utilities { /// /// The is responsible for managing the loading Rive file, artboard, state machine. It handles the core logic for loading and unloading artboards from Rive Files. /// internal class ArtboardLoadHelper { public enum LoadErrorType { InvalidArguments = 0, ArtboardNotFound = 1, StateMachineNotFound = 2, } public readonly struct LoadErrorEventData { public LoadErrorType ErrorType { get; } public string Message { get; } public LoadErrorEventData(LoadErrorType errorType, string message = null) { ErrorType = errorType; Message = message; } } internal struct DataBindingLoadInfo { public RiveWidget.DataBindingMode BindingMode { get; } public string InstanceName { get; } public DataBindingLoadInfo(RiveWidget.DataBindingMode bindingMode, string instanceName) { BindingMode = bindingMode; InstanceName = instanceName; } } internal struct LoadResult { public bool Success { get; } public LoadErrorEventData ErrorData { get; } public LoadResult(bool success, LoadErrorEventData errorData = default) { Success = success; ErrorData = errorData; } } private Artboard m_artboard; private StateMachine m_stateMachine; private File m_file; private ArtboardRenderObject m_renderObject; private float originalArtboardWidth; private float originalArtboardHeight; private bool m_isLoaded = false; private List m_reportedEvents = new List(); public Artboard Artboard => m_artboard; public StateMachine StateMachine => m_stateMachine; public File File { get => m_file; } public ArtboardRenderObject RenderObject => m_renderObject; public float OriginalArtboardWidth => originalArtboardWidth; public float OriginalArtboardHeight => originalArtboardHeight; public bool IsLoaded { get => m_isLoaded; private set => m_isLoaded = value; } public delegate void RiveEventDelegate(ReportedEvent report); public delegate void RiveLoadErrorDelegate(LoadErrorEventData eventData); public delegate void RiveLoadCompleteDelegate(); public delegate void RiveRenderStateChangeDelegate(); public event RiveEventDelegate OnRiveEventReported; public LoadResult Load(File file, Fit fit, Alignment alignment, string artboardName, string stateMachineName, float scaleFactor, DataBindingLoadInfo bindingInfo) { CleanUpBeforeLoad(); if (file == null) { IsLoaded = false; return new LoadResult(false, new LoadErrorEventData(LoadErrorType.InvalidArguments, "File is null")); } m_file = file; m_artboard = string.IsNullOrEmpty(artboardName) ? m_file.Artboard(0) : m_file.Artboard(artboardName); if (m_artboard == null) { IsLoaded = false; return new LoadResult(false, new LoadErrorEventData(LoadErrorType.ArtboardNotFound, $"Artboard {artboardName} not found in file")); } originalArtboardWidth = m_artboard.Width; originalArtboardHeight = m_artboard.Height; m_stateMachine = string.IsNullOrEmpty(stateMachineName) ? m_artboard.StateMachine(0) : m_artboard.StateMachine(stateMachineName); if (m_stateMachine == null) { IsLoaded = false; return new LoadResult(false, new LoadErrorEventData(LoadErrorType.StateMachineNotFound, $"State machine {stateMachineName} not found in artboard {artboardName}")); } var viewModelInstance = GetVmInstanceToApply(bindingInfo.BindingMode, m_artboard, bindingInfo.InstanceName); if (viewModelInstance != null) { m_stateMachine.BindViewModelInstance(viewModelInstance); } m_renderObject = CreateRenderObject(m_artboard, alignment, fit, scaleFactor); IsLoaded = true; return new LoadResult(true); } private ViewModelInstance GetVmInstanceToApply(RiveWidget.DataBindingMode bindingMode, Artboard artboard, string instanceName = null) { ViewModelInstance vmInstance = null; switch (bindingMode) { case RiveWidget.DataBindingMode.Manual: break; case RiveWidget.DataBindingMode.AutoBindDefault: vmInstance = artboard.DefaultViewModel?.CreateDefaultInstance(); break; case RiveWidget.DataBindingMode.AutoBindSelected: vmInstance = artboard.DefaultViewModel?.CreateInstanceByName(instanceName); break; default: break; } return vmInstance; } public void Tick(float deltaTime, RiveWidget.EventPoolingMode poolingMode, float speed) { if (m_stateMachine == null) { return; } m_reportedEvents.Clear(); m_stateMachine.ReportedEvents(m_reportedEvents); for (int i = 0; i < m_reportedEvents.Count; i++) { var evt = m_reportedEvents[i]; OnRiveEventReported?.Invoke(evt); // If pooling is enabled, auto-dispose the event if (poolingMode == RiveWidget.EventPoolingMode.Enabled) { evt.Dispose(); } } m_stateMachine.Advance(deltaTime * speed); // Legacy callback propagation behavior if (RiveWidget.PropertyCallbackApproach == RiveWidget.DataBindingPropertyCallbackApproach.Propagation) { m_stateMachine.ViewModelInstance?.HandleCallbacks(); } } private ArtboardRenderObject CreateRenderObject(Artboard artboard, Alignment alignment, Fit fit, float scaleFactor) { ArtboardRenderObject existingRenderObject = m_renderObject as ArtboardRenderObject; if (existingRenderObject != null) { existingRenderObject.Init(artboard, alignment, fit, scaleFactor); return existingRenderObject; } return new ArtboardRenderObject(artboard, alignment, fit, scaleFactor); } private void CleanUpBeforeLoad() { m_stateMachine?.Dispose(); m_stateMachine = null; m_artboard?.Dispose(); m_artboard = null; // File ownership/lifecycle is managed by the caller (e.g., RiveWidget), so do not dispose it here. m_file = null; } /// /// Calculates the effective scale factor based on the scaling mode and provided parameters. /// /// The scaling mode to use. /// The scale factor to apply. /// The original size of the artboard. /// The frame rect where the artboard will be displayed. /// The reference DPI to use for scaling. /// The fallback DPI to use if the current screen DPI is not available. /// The screen DPI to use for scaling. If not provided, Screen.dpi will be used. public static float CalculateEffectiveScaleFactor( LayoutScalingMode scalingMode, float scaleFactor, Vector2 originalArtboardSize, Rect frameRect, float referenceDPI, float fallbackDPI = 96f, float screenDPI = -1f ) { float originalWidth = originalArtboardSize.x; float originalHeight = originalArtboardSize.y; switch (scalingMode) { case LayoutScalingMode.ConstantPixelSize: return scaleFactor; case LayoutScalingMode.ReferenceArtboardSize: { if (originalWidth <= 0 || originalHeight <= 0) { return 1.0f; } float widthScale = frameRect.width / originalWidth; float heightScale = frameRect.height / originalHeight; // Using the height scale gives us a match with the Rive Editor float resolutionScale = heightScale; return scaleFactor * resolutionScale; } case LayoutScalingMode.ConstantPhysicalSize: { float dpi = screenDPI > 0f ? screenDPI : Screen.dpi; if (dpi <= 0f) { dpi = fallbackDPI; } float devicePixelRatio = dpi / referenceDPI; return scaleFactor * devicePixelRatio; } default: return 1.0f; } } /// /// Calculates the new artboard dimensions based on the frame rect and effective scale. /// /// Returns true if resize was successful, false if invalid values were encountered. public static bool CalculateArtboardDimensionsForLayout( Rect frameRect, float effectiveScaleFactor, out float newWidth, out float newHeight ) { newWidth = 0f; newHeight = 0f; // Guard against invalid scale if (effectiveScaleFactor <= 0 || float.IsNaN(effectiveScaleFactor) || float.IsInfinity(effectiveScaleFactor)) { DebugLogger.Instance.LogWarning($"Invalid effective scale: {effectiveScaleFactor}"); return false; } newWidth = frameRect.width / effectiveScaleFactor; newHeight = frameRect.height / effectiveScaleFactor; // Guard against invalid dimensions if (float.IsNaN(newWidth) || float.IsInfinity(newWidth) || float.IsNaN(newHeight) || float.IsInfinity(newHeight)) { DebugLogger.Instance.LogWarning($"Invalid artboard dimensions calculated. Width: {newWidth}, Height: {newHeight}"); return false; } return true; } public void Dispose() { CleanUpBeforeLoad(); m_renderObject = null; } } }