Files
BABA_YAGA/Packages/app.rive.rive-unity/Runtime/DataBinding/Helpers/ViewModelInstancePropertyHandlersFactory.cs

422 lines
17 KiB
C#
Raw Normal View History

2026-05-19 17:39:03 +07:00
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Rive.Utils;
namespace Rive
{
/// <summary>
/// Factory class responsible for creating property handlers for the ViewModelInstance class.
/// This centralizes all property-related functionality outside of ViewModelInstance.
/// </summary>
internal static class ViewModelInstancePropertyHandlersFactory
{
#region Property Result Types
/// <summary>
/// Represents the result of a property getter operation.
/// </summary>
private readonly struct PropertyGetterResult
{
public enum EnumTypeOption
{
None = 0,
CustomEnum = 1,
SystemEnum = 2
}
public readonly IntPtr PropertyPtr { get; }
public readonly EnumTypeOption EnumType { get; }
public readonly nuint? EnumIndex { get; }
public ViewModelInstance RootViewModelInstance { get; }
public PropertyGetterResult(IntPtr property, ViewModelInstance rootVmInstance, EnumTypeOption enumType = EnumTypeOption.None, nuint? enumIndex = null)
{
PropertyPtr = property;
EnumType = enumType;
EnumIndex = enumIndex;
RootViewModelInstance = rootVmInstance;
}
}
/// <summary>
/// Delegate for property getter functions
/// </summary>
private delegate PropertyGetterResult PropertyGetter(ViewModelInstance instanceToGetPropertyFrom, string path, ViewModelInstance rootInstance);
#endregion
#region Property Cache
#endregion
#region Property Handlers
private static readonly Dictionary<Type, (PropertyGetter getter, Func<PropertyGetterResult, ViewModelInstancePrimitiveProperty> creator)>
PropertyHandlers = InitializePropertyHandlers();
private static Dictionary<Type, (PropertyGetter getter, Func<PropertyGetterResult, ViewModelInstancePrimitiveProperty> creator)> InitializePropertyHandlers()
{
return new Dictionary<Type, (PropertyGetter, Func<PropertyGetterResult, ViewModelInstancePrimitiveProperty>)>
{
{ typeof(ViewModelInstanceEnumProperty), CreateEnumPropertyHandler() },
{ typeof(ViewModelInstanceTriggerProperty), CreateTriggerPropertyHandler() },
{ typeof(ViewModelInstanceBooleanProperty), CreateBooleanPropertyHandler() },
{ typeof(ViewModelInstanceNumberProperty), CreateNumberPropertyHandler() },
{ typeof(ViewModelInstanceStringProperty), CreateStringPropertyHandler() },
{ typeof(ViewModelInstanceColorProperty), CreateColorPropertyHandler() },
{ typeof(ViewModelInstanceImageProperty), CreateImagePropertyHandler() },
{ typeof(ViewModelInstanceListProperty), CreateListPropertyHandler() },
{ typeof(ViewModelInstanceArtboardProperty), CreateArtboardPropertyHandler() }
};
}
/// <summary>
/// Creates a handler for enum properties
/// </summary>
private static (PropertyGetter getter,
Func<PropertyGetterResult, ViewModelInstancePrimitiveProperty> creator) CreateEnumPropertyHandler()
{
return (
(instance, path, rootInstance) =>
{
// If the file that the instance was loaded from is no longer available, this will be null
IntPtr nativeFilePtr = instance.RiveFile != null ? instance.RiveFile.NativeFile : IntPtr.Zero;
var info = getEnumPropertyInfoFromViewModelInstance(
instance.NativeSafeHandle,
nativeFilePtr,
path);
return new PropertyGetterResult(
info.propertyPtr,
rootInstance,
PropertyGetterResult.EnumTypeOption.CustomEnum,
info.enumIndex);
},
(result) =>
{
// If the property was not found, or if the property is not an enum, this will be true
if (result.PropertyPtr == IntPtr.Zero)
{
return null;
}
IReadOnlyList<ViewModelEnumData> enumsForFile = result.RootViewModelInstance.RiveFile != null ? result.RootViewModelInstance.RiveFile.ViewModelEnums : null;
bool isValidIndex = result.EnumIndex.HasValue && result.EnumIndex.Value >= 0 && (int)result.EnumIndex.Value < enumsForFile.Count;
// If the enums are included in the file, we can reuse them across multiple instances
// Otherwise, we'll have to fetch the enum values from the instance. This happens in the ViewModelInstanceEnumProperty constructor if we don't have the enum values already.
if (isValidIndex && enumsForFile != null)
{
string[] enumValues = enumsForFile[(int)result.EnumIndex.Value].ValuesArray;
return new ViewModelInstanceEnumProperty(
result.PropertyPtr,
result.RootViewModelInstance,
enumValues);
}
else
{
return new ViewModelInstanceEnumProperty(
result.PropertyPtr,
result.RootViewModelInstance);
}
}
);
}
/// <summary>
/// Creates a handler for trigger properties
/// </summary>
private static (PropertyGetter getter,
Func<PropertyGetterResult, ViewModelInstancePrimitiveProperty> creator) CreateTriggerPropertyHandler()
{
return (
(instance, path, rootInstance) => new PropertyGetterResult(
getViewModelInstanceTriggerProperty(instance.NativeSafeHandle, path),
rootInstance,
PropertyGetterResult.EnumTypeOption.None),
(result) => new ViewModelInstanceTriggerProperty(
result.PropertyPtr,
result.RootViewModelInstance)
);
}
/// <summary>
/// Creates a handler for boolean properties
/// </summary>
private static (PropertyGetter getter,
Func<PropertyGetterResult, ViewModelInstancePrimitiveProperty> creator) CreateBooleanPropertyHandler()
{
return (
(instance, path, rootInstance) => new PropertyGetterResult(
getViewModelInstanceBooleanProperty(instance.NativeSafeHandle, path),
rootInstance,
PropertyGetterResult.EnumTypeOption.None),
(result) => new ViewModelInstanceBooleanProperty(
result.PropertyPtr,
result.RootViewModelInstance)
);
}
/// <summary>
/// Creates a handler for number properties
/// </summary>
private static (PropertyGetter getter,
Func<PropertyGetterResult, ViewModelInstancePrimitiveProperty> creator) CreateNumberPropertyHandler()
{
return (
(instance, path, rootInstance) => new PropertyGetterResult(
getViewModelInstanceNumberProperty(instance.NativeSafeHandle, path),
rootInstance,
PropertyGetterResult.EnumTypeOption.None),
(result) => new ViewModelInstanceNumberProperty(
result.PropertyPtr,
result.RootViewModelInstance)
);
}
/// <summary>
/// Creates a handler for string properties
/// </summary>
private static (PropertyGetter getter,
Func<PropertyGetterResult, ViewModelInstancePrimitiveProperty> creator) CreateStringPropertyHandler()
{
return (
(instance, path, rootInstance) => new PropertyGetterResult(
getViewModelInstanceStringProperty(instance.NativeSafeHandle, path),
rootInstance,
PropertyGetterResult.EnumTypeOption.None),
(result) => new ViewModelInstanceStringProperty(
result.PropertyPtr,
result.RootViewModelInstance)
);
}
/// <summary>
/// Creates a handler for color properties
/// </summary>
private static (PropertyGetter getter,
Func<PropertyGetterResult, ViewModelInstancePrimitiveProperty> creator) CreateColorPropertyHandler()
{
return (
(instance, path, rootInstance) => new PropertyGetterResult(
getViewModelInstanceColorProperty(instance.NativeSafeHandle, path),
rootInstance,
PropertyGetterResult.EnumTypeOption.None),
(result) => new ViewModelInstanceColorProperty(
result.PropertyPtr,
result.RootViewModelInstance)
);
}
/// <summary>
/// Creates a handler for image properties
/// </summary>
private static (PropertyGetter getter,
Func<PropertyGetterResult, ViewModelInstancePrimitiveProperty> creator) CreateImagePropertyHandler()
{
return (
(instance, path, rootInstance) => new PropertyGetterResult(
getViewModelInstanceImageProperty(instance.NativeSafeHandle, path),
rootInstance),
(result) => new ViewModelInstanceImageProperty(
result.PropertyPtr,
result.RootViewModelInstance)
);
}
private static (PropertyGetter getter,
Func<PropertyGetterResult, ViewModelInstancePrimitiveProperty> creator) CreateListPropertyHandler()
{
return (
(instance, path, rootInstance) => new PropertyGetterResult(
getViewModelInstanceListProperty(instance.NativeSafeHandle, path),
rootInstance,
PropertyGetterResult.EnumTypeOption.None),
(result) => new ViewModelInstanceListProperty(
result.PropertyPtr,
result.RootViewModelInstance)
);
}
private static (PropertyGetter getter,
Func<PropertyGetterResult, ViewModelInstancePrimitiveProperty> creator) CreateArtboardPropertyHandler()
{
return (
(instance, path, rootInstance) => new PropertyGetterResult(
getViewModelInstanceArtboardProperty(instance.NativeSafeHandle, path),
rootInstance),
(result) => new ViewModelInstanceArtboardProperty(
result.PropertyPtr,
result.RootViewModelInstance)
);
}
#endregion
#region Public Property Fetching API
/// <summary>
/// Gets a property of the specified type from a view model instance.
/// </summary>
/// <typeparam name="T">The type of property to get</typeparam>
/// <param name="instance">The view model instance to get the property from</param>
/// <param name="path">The path to the property</param>
/// <returns>The property instance or null if not found</returns>
public static T GetPrimitiveProperty<T>(ViewModelInstance instance, string path) where T : ViewModelInstanceProperty
{
if (!PropertyHandlers.TryGetValue(typeof(T), out var handler))
{
DebugLogger.Instance.LogError("Property type not supported: " + typeof(T).Name);
return null;
}
var result = GetPropertyPointer(handler.getter, instance, path, instance);
if (result.PropertyPtr == IntPtr.Zero)
{
DebugLogger.Instance.LogError("Property not found: " + path);
return null;
}
T instanceAsExpectedType = null;
// Check if we have already created an instance of this property
if (ViewModelInstanceProperty.TryGetGloballyCachedVMPropertyForPointer(result.PropertyPtr, out var cachedProperty))
{
bool isStale = cachedProperty is ViewModelInstancePrimitiveProperty cachedPrim &&
cachedPrim.RootInstance != instance;
// We want to catch cases where the property was created as a different type than expected
if (isStale)
{
ViewModelInstanceProperty.RemoveCachedPropertyForPointer(result.PropertyPtr);
}
else
{
instanceAsExpectedType = cachedProperty as T;
if (instanceAsExpectedType == null)
{
DebugLogger.Instance.LogError("Failed to get property: " + path + ". Expected type: " + typeof(T).Name);
return null;
}
return instanceAsExpectedType;
}
}
// Create a new property instance
var propInstance = handler.creator(result);
ViewModelInstanceProperty.AddGloballyCachedVMPropertyForPointer(result.PropertyPtr, propInstance);
instanceAsExpectedType = propInstance as T;
return instanceAsExpectedType;
}
private static PropertyGetterResult GetPropertyPointer(
PropertyGetter getter,
ViewModelInstance instance,
string path,
ViewModelInstance rootInstance)
{
return getter(instance, path, rootInstance);
}
#if UNITY_EDITOR
/// <summary>
/// Editor-only utility to get the enum data for a property at a given path.
/// </summary>
/// <param name="vmInstance"></param>
/// <param name="path"></param>
/// <returns></returns>
internal static ViewModelEnumData GetEnumForPropertyAtPath(ViewModelInstance vmInstance, string path)
{
if (vmInstance == null || vmInstance.RiveFile == null)
{
return null;
}
IntPtr nativeFilePtr = vmInstance.RiveFile != null ? vmInstance.RiveFile.NativeFile : IntPtr.Zero;
var info = getEnumPropertyInfoFromViewModelInstance(
vmInstance.NativeSafeHandle,
nativeFilePtr,
path);
if (info.propertyPtr == IntPtr.Zero)
{
return null;
}
if (info.enumIndex < 0)
{
return null;
}
IReadOnlyList<ViewModelEnumData> enumsForFile = vmInstance.RiveFile.ViewModelEnums;
if (enumsForFile == null || (int)info.enumIndex >= enumsForFile.Count)
{
return null;
}
return enumsForFile[(int)info.enumIndex];
}
#endif
#endregion
#region Native Calls
[StructLayout(LayoutKind.Sequential)]
private struct ViewModelInstanceEnumPropertyInfo
{
/// <summary>
/// The pointer to the instance property.
/// </summary>
public IntPtr propertyPtr;
/// <summary>
/// The index of the enum value in the Rive file. Allows us to share the same enum value across multiple instances.
/// </summary>
public nuint enumIndex;
}
[DllImport(NativeLibrary.name)]
private static extern IntPtr getViewModelInstanceNumberProperty(ViewModelInstanceSafeHandle instanceValue, string path);
[DllImport(NativeLibrary.name)]
private static extern IntPtr getViewModelInstanceBooleanProperty(ViewModelInstanceSafeHandle instanceValue, string path);
[DllImport(NativeLibrary.name)]
private static extern IntPtr getViewModelInstanceTriggerProperty(ViewModelInstanceSafeHandle instanceValue, string path);
[DllImport(NativeLibrary.name)]
private static extern IntPtr getViewModelInstanceStringProperty(ViewModelInstanceSafeHandle instanceValue, string path);
[DllImport(NativeLibrary.name)]
private static extern IntPtr getViewModelInstanceColorProperty(ViewModelInstanceSafeHandle instanceValue, string path);
[DllImport(NativeLibrary.name)]
private static extern IntPtr getViewModelInstanceImageProperty(ViewModelInstanceSafeHandle instanceValue, string path);
[DllImport(NativeLibrary.name)]
private static extern IntPtr getViewModelInstanceListProperty(ViewModelInstanceSafeHandle instanceValue, string path);
[DllImport(NativeLibrary.name)]
private static extern IntPtr getViewModelInstanceArtboardProperty(ViewModelInstanceSafeHandle instanceValue, string path);
[DllImport(NativeLibrary.name)]
private static extern ViewModelInstanceEnumPropertyInfo getEnumPropertyInfoFromViewModelInstance(
ViewModelInstanceSafeHandle instanceValue,
IntPtr fileWrapper,
string path);
#endregion
}
}