cz.xprees.variables 1.0.18 → 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,9 +5,14 @@
5
5
  using System;
6
6
  using UnityEngine.Events;
7
7
  using Xprees.Core;
8
+ using Xprees.Variables.Utils;
8
9
 
9
10
  namespace Xprees.Variables.Base
10
11
  {
12
+ /// <summary>
13
+ /// Base class for all variable references used in the game. It can either use an inlined value or reference a VariableBaseSO Scriptable Object. The Value property abstracts this choice away, so users of ReferenceBase don't have to care about it.
14
+ /// </summary>
15
+ /// <typeparam name="T">Unity Serializable</typeparam>
11
16
  [Serializable]
12
17
  public class ReferenceBase<T> : IResettable
13
18
  {
@@ -17,7 +22,37 @@ namespace Xprees.Variables.Base
17
22
  public T inlinedValue;
18
23
  public VariableBaseSO<T> variable;
19
24
 
20
- public UnityAction<T> onValueChanged = delegate { };
25
+ // Internal event for inlined value changes, since VariableBaseSO already has its own onValueChanged event.
26
+ private UnityAction<T> _onInlinedValueChanged;
27
+
28
+ // ReSharper disable once InconsistentNaming
29
+ /// Event invoked when Value changes.
30
+ public event UnityAction<T> onValueChanged
31
+ {
32
+ add
33
+ {
34
+ var shouldRedirectToVarEvent = !useInlined && variable != null;
35
+ if (shouldRedirectToVarEvent)
36
+ {
37
+ variable.onValueChanged += value;
38
+ return;
39
+ }
40
+
41
+ _onInlinedValueChanged += value;
42
+ }
43
+
44
+ remove
45
+ {
46
+ var shouldRedirectToVarEvent = !useInlined && variable != null;
47
+ if (shouldRedirectToVarEvent)
48
+ {
49
+ variable.onValueChanged -= value;
50
+ return;
51
+ }
52
+
53
+ _onInlinedValueChanged -= value;
54
+ }
55
+ }
21
56
 
22
57
  public ReferenceBase()
23
58
  {
@@ -38,12 +73,12 @@ namespace Xprees.Variables.Base
38
73
  if (useInlined)
39
74
  {
40
75
  inlinedValue = value;
41
- onValueChanged?.Invoke(value);
76
+ _onInlinedValueChanged?.Invoke(value); // Invoke inlined value change event
42
77
  return;
43
78
  }
44
79
 
80
+ // VariableBaseSO will invoke its own onValueChanged event, so no need to invoke here.
45
81
  variable.SetValue(value);
46
- onValueChanged?.Invoke(value); // ignores that variable has its own onValueChanged
47
82
  }
48
83
  }
49
84
 
@@ -53,14 +88,14 @@ namespace Xprees.Variables.Base
53
88
  {
54
89
  if (!useInlined) return; // Variables does that by themselves
55
90
 
56
- _defaultInlinedValue = inlinedValue;
91
+ _defaultInlinedValue = CloningTools.Clone(inlinedValue);
57
92
  }
58
93
 
59
94
  public virtual void ResetState()
60
95
  {
61
96
  if (useInlined)
62
97
  {
63
- inlinedValue = _defaultInlinedValue;
98
+ inlinedValue = CloningTools.Clone(_defaultInlinedValue);
64
99
  return;
65
100
  }
66
101
 
@@ -1,6 +1,7 @@
1
1
  using UnityEngine;
2
2
  using UnityEngine.Events;
3
3
  using Xprees.Core;
4
+ using Xprees.Variables.Utils;
4
5
 
5
6
  namespace Xprees.Variables.Base
6
7
  {
@@ -22,7 +23,7 @@ namespace Xprees.Variables.Base
22
23
  /// Base class for all variables ScriptableObjects.
23
24
  /// Object holds the state on runtime, but reset every time OnEnable to defaultValue.
24
25
  /// </summary>
25
- /// <typeparam name="T">Unity Serializable</typeparam>
26
+ /// <typeparam name="T">Unity Serializable or System.Serializable</typeparam>
26
27
  public class VariableBaseSO<T> : VariableBaseSO
27
28
  {
28
29
  [Tooltip("Value to which the variable will be reset on OnEnable or ResetState call.")]
@@ -64,7 +65,6 @@ namespace Xprees.Variables.Base
64
65
  ForceResetState();
65
66
  }
66
67
 
67
- public override void ForceResetState() => CurrentValue = defaultValue;
68
-
68
+ public override void ForceResetState() => CurrentValue = CloningTools.Clone(defaultValue);
69
69
  }
70
70
  }
@@ -0,0 +1,119 @@
1
+ using System;
2
+ using System.Collections;
3
+ using System.Runtime.CompilerServices;
4
+ using UnityEngine;
5
+ using UnityEngine.Scripting;
6
+ using Object = UnityEngine.Object;
7
+
8
+ namespace Xprees.Variables.Utils
9
+ {
10
+ public static class CloningTools
11
+ {
12
+ /// <summary>
13
+ /// Helper to clone a value of type T. It handles value types, reference types that implement ICloneable, and other reference types by serializing and deserializing them using Unity's JsonUtility. Note that the serialization approach only works for types that are compatible with JsonUtility (e.g., they must be Unity serializable). For more complex types, you might need to implement custom cloning logic or use a different serialization method.
14
+ /// </summary>
15
+ /// <param name="value">Value to clone</param>
16
+ /// <typeparam name="T">primitive/value type/Unity serializable</typeparam>
17
+ /// <returns>A clone of input value</returns>
18
+ [Preserve]
19
+ public static T Clone<T>(T value)
20
+ {
21
+ if (value == null) return default;
22
+
23
+ // Value types can be returned directly since they are copied by value. However, we can optimize for value types that do not contain references to avoid unnecessary cloning.
24
+ var canReturnAsIs = typeof(T).IsValueType && !RuntimeHelpers.IsReferenceOrContainsReferences<T>();
25
+ if (canReturnAsIs) return value;
26
+
27
+ if (value is ICloneable cloneable) return (T) cloneable.Clone();
28
+
29
+ // Handle collections (List<T>, arrays, etc.) - JsonUtility doesn't support them directly
30
+ if (value is IList sourceList)
31
+ {
32
+ var listType = value.GetType();
33
+ if (listType.IsArray)
34
+ {
35
+ var elementType = listType.GetElementType();
36
+ var array = Array.CreateInstance(elementType!, sourceList.Count);
37
+ for (var i = 0; i < sourceList.Count; i++)
38
+ {
39
+ var clonedItem = CloneItem(sourceList[i]);
40
+ array.SetValue(clonedItem, i);
41
+ }
42
+
43
+ return (T) (object) array;
44
+ }
45
+
46
+ var clonedList = (IList) Activator.CreateInstance(listType);
47
+ foreach (var item in sourceList)
48
+ {
49
+ clonedList.Add(CloneItem(item));
50
+ }
51
+
52
+ return (T) clonedList;
53
+ }
54
+
55
+ // For other types, we can try to serialize and deserialize to create a deep clone.
56
+ // JsonUtility only supports a subset of types. Guard against unsupported types and failed serialization.
57
+ var type = typeof(T);
58
+
59
+ // Allow UnityEngine.Object subclasses, or types explicitly marked as [Serializable].
60
+ var isUnityObject = typeof(Object).IsAssignableFrom(type);
61
+ var isSerializable = Attribute.IsDefined(type, typeof(SerializableAttribute));
62
+ if (!isUnityObject && !isSerializable)
63
+ {
64
+ throw new InvalidOperationException(
65
+ $"Type '{type.FullName}' is not supported for JsonUtility-based cloning. " +
66
+ "Ensure the type is Unity-serializable (e.g., marked with [Serializable]) or implements ICloneable.");
67
+ }
68
+
69
+ try
70
+ {
71
+ var json = JsonUtility.ToJson(value);
72
+ if (string.IsNullOrEmpty(json) || json == "{}" || json == "null")
73
+ {
74
+ throw new InvalidOperationException(
75
+ $"JsonUtility failed to serialize an instance of type '{type.FullName}' " +
76
+ "for cloning. The resulting clone would be incomplete or empty.");
77
+ }
78
+
79
+ return JsonUtility.FromJson<T>(json);
80
+ }
81
+ catch (Exception ex) when (ex is not InvalidOperationException)
82
+ {
83
+ throw new InvalidOperationException(
84
+ $"AOT/IL2CPP error cloning type '{type.FullName}'. " +
85
+ "Ensure the type is preserved and AOT-compatible.", ex);
86
+ }
87
+ }
88
+
89
+ /// Helper to clone individual items in collections, using the same logic as the main Clone method.
90
+ private static object CloneItem(object item)
91
+ {
92
+ if (item == null) return null;
93
+
94
+ var itemType = item.GetType();
95
+
96
+ // Value types are copied by value
97
+ if (itemType.IsValueType) return item;
98
+
99
+ // ICloneable
100
+ if (item is ICloneable cloneable) return cloneable.Clone();
101
+
102
+ // UnityEngine.Object - return reference (can't deep clone ScriptableObjects easily)
103
+ if (item is Object) return item;
104
+
105
+ // Try JSON serialization for other serializable types
106
+ if (Attribute.IsDefined(itemType, typeof(SerializableAttribute)))
107
+ {
108
+ var json = JsonUtility.ToJson(item);
109
+ if (!string.IsNullOrEmpty(json) && json != "{}" && json != "null")
110
+ {
111
+ return JsonUtility.FromJson(json, itemType);
112
+ }
113
+ }
114
+
115
+ // Fallback: return the same reference
116
+ return item;
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: c9c3981f4d224c10860d1627fb51bd33
3
+ timeCreated: 1770985135
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: cd80394a0cb2475ca81f67dcd7bb38d8
3
+ timeCreated: 1770985115
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cz.xprees.variables",
3
3
  "displayName": "Variables",
4
- "version": "1.0.18",
4
+ "version": "1.0.20",
5
5
  "unity": "2021.3",
6
6
  "description": "This package contains a system for creating variables based on ScriptableObjects in Editor.",
7
7
  "license": "Apache-2.0",