cz.xprees.variables 1.0.19 → 1.0.22

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.
@@ -3,20 +3,47 @@
3
3
  // ------------------------------------------------------------------------------------------------------
4
4
 
5
5
  using System;
6
+ using UnityEngine;
6
7
  using UnityEngine.Events;
7
8
  using Xprees.Core;
9
+ using Xprees.Variables.Utils;
8
10
 
9
11
  namespace Xprees.Variables.Base
10
12
  {
13
+ /// <summary>
14
+ /// 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.
15
+ /// </summary>
16
+ /// <typeparam name="T">Unity Serializable</typeparam>
11
17
  [Serializable]
12
- public class ReferenceBase<T> : IResettable
18
+ public class ReferenceBase<T> : IResettable, ISerializationCallbackReceiver
13
19
  {
20
+ private bool _hasCapturedBaseline; // Used to check if baseline has been captured
14
21
  private T _defaultInlinedValue; // Used to reset state of inlined value
15
22
 
16
23
  public bool useInlined = true;
17
24
  public T inlinedValue;
18
25
  public VariableBaseSO<T> variable;
19
26
 
27
+ public void OnBeforeSerialize()
28
+ {
29
+ }
30
+
31
+ public void OnAfterDeserialize()
32
+ {
33
+ if (!PlayModeStateTracker.IsPlaying)
34
+ {
35
+ _defaultInlinedValue = CloningTools.Clone(inlinedValue);
36
+ _hasCapturedBaseline = false;
37
+ return;
38
+ }
39
+
40
+ if (!_hasCapturedBaseline)
41
+ {
42
+ _defaultInlinedValue = CloningTools.Clone(inlinedValue);
43
+ _hasCapturedBaseline = true;
44
+ }
45
+ }
46
+
20
47
  // Internal event for inlined value changes, since VariableBaseSO already has its own onValueChanged event.
21
48
  private UnityAction<T> _onInlinedValueChanged;
22
49
 
@@ -26,7 +53,7 @@ namespace Xprees.Variables.Base
26
53
  {
27
54
  add
28
55
  {
29
- var shouldRedirectToVarEvent = !useInlined && variable != null;
56
+ var shouldRedirectToVarEvent = !useInlined && variable;
30
57
  if (shouldRedirectToVarEvent)
31
58
  {
32
59
  variable.onValueChanged += value;
@@ -38,7 +65,7 @@ namespace Xprees.Variables.Base
38
65
 
39
66
  remove
40
67
  {
41
- var shouldRedirectToVarEvent = !useInlined && variable != null;
68
+ var shouldRedirectToVarEvent = !useInlined && variable;
42
69
  if (shouldRedirectToVarEvent)
43
70
  {
44
71
  variable.onValueChanged -= value;
@@ -58,6 +85,7 @@ namespace Xprees.Variables.Base
58
85
  useInlined = true;
59
86
  inlinedValue = value;
60
87
  _defaultInlinedValue = value;
88
+ _hasCapturedBaseline = true;
61
89
  }
62
90
 
63
91
  public T Value
@@ -83,14 +111,15 @@ namespace Xprees.Variables.Base
83
111
  {
84
112
  if (!useInlined) return; // Variables does that by themselves
85
113
 
86
- _defaultInlinedValue = inlinedValue;
114
+ _defaultInlinedValue = CloningTools.Clone(inlinedValue);
115
+ _hasCapturedBaseline = true;
87
116
  }
88
117
 
89
118
  public virtual void ResetState()
90
119
  {
91
120
  if (useInlined)
92
121
  {
93
- inlinedValue = _defaultInlinedValue;
122
+ inlinedValue = CloningTools.Clone(_defaultInlinedValue);
94
123
  return;
95
124
  }
96
125
 
@@ -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.")]
@@ -42,6 +43,13 @@ namespace Xprees.Variables.Base
42
43
  get => currentValue;
43
44
  set
44
45
  {
46
+ #if UNITY_EDITOR
47
+ // If not already captured in Editor capture before changing the value
48
+ if (PlayModeStateTracker.IsPlaying)
49
+ {
50
+ StateSnapshotService.EnsureCaptured(this);
51
+ }
52
+ #endif
45
53
  currentValue = value;
46
54
  onValueChanged?.Invoke(value);
47
55
  }
@@ -64,7 +72,6 @@ namespace Xprees.Variables.Base
64
72
  ForceResetState();
65
73
  }
66
74
 
67
- public override void ForceResetState() => CurrentValue = defaultValue;
68
-
75
+ public override void ForceResetState() => CurrentValue = CloningTools.Clone(defaultValue);
69
76
  }
70
77
  }
@@ -38,9 +38,14 @@ namespace Xprees.Variables.Base
38
38
 
39
39
  public override void ResetState()
40
40
  {
41
- base.ResetState();
42
- variable?.ResetState();
41
+ if (protectedDontReset) return;
42
+ ForceResetState();
43
+ }
44
+
45
+ public override void ForceResetState()
46
+ {
43
47
  disableWrite?.ResetState();
48
+ variable?.ResetState();
44
49
  }
45
50
  }
46
51
  }
@@ -0,0 +1,129 @@
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
+ // Strings are immutable in .NET, so return directly without JsonUtility
28
+ if (typeof(T) == typeof(string)) return value;
29
+
30
+ // UnityEngine.Object references (ScriptableObjects, MonoBehaviours, Assets, GameObjects)
31
+ // are reference-assigned; we must not deep-clone or call CreateInstance during serialization.
32
+ if (typeof(Object).IsAssignableFrom(typeof(T)) || value is Object) return value;
33
+
34
+ if (value is ICloneable cloneable) return (T) cloneable.Clone();
35
+
36
+ // Handle collections (List<T>, arrays, etc.) - JsonUtility doesn't support them directly
37
+ if (value is IList sourceList)
38
+ {
39
+ var listType = value.GetType();
40
+ if (listType.IsArray)
41
+ {
42
+ var elementType = listType.GetElementType();
43
+ var array = Array.CreateInstance(elementType!, sourceList.Count);
44
+ for (var i = 0; i < sourceList.Count; i++)
45
+ {
46
+ var clonedItem = CloneItem(sourceList[i]);
47
+ array.SetValue(clonedItem, i);
48
+ }
49
+
50
+ return (T) (object) array;
51
+ }
52
+
53
+ var clonedList = (IList) Activator.CreateInstance(listType);
54
+ foreach (var item in sourceList)
55
+ {
56
+ clonedList.Add(CloneItem(item));
57
+ }
58
+
59
+ return (T) clonedList;
60
+ }
61
+
62
+ // For other types, we can try to serialize and deserialize to create a deep clone.
63
+ // JsonUtility only supports a subset of types. Guard against unsupported types and failed serialization.
64
+ var type = typeof(T);
65
+
66
+ // Allow UnityEngine.Object subclasses, or types explicitly marked as [Serializable].
67
+ var isUnityObject = typeof(Object).IsAssignableFrom(type);
68
+ var isSerializable = Attribute.IsDefined(type, typeof(SerializableAttribute));
69
+ if (!isUnityObject && !isSerializable)
70
+ {
71
+ throw new InvalidOperationException(
72
+ $"Type '{type.FullName}' is not supported for JsonUtility-based cloning. " +
73
+ "Ensure the type is Unity-serializable (e.g., marked with [Serializable]) or implements ICloneable.");
74
+ }
75
+
76
+ try
77
+ {
78
+ var json = JsonUtility.ToJson(value);
79
+ if (string.IsNullOrEmpty(json) || json == "{}" || json == "null")
80
+ {
81
+ throw new InvalidOperationException(
82
+ $"JsonUtility failed to serialize an instance of type '{type.FullName}' " +
83
+ "for cloning. The resulting clone would be incomplete or empty.");
84
+ }
85
+
86
+ return JsonUtility.FromJson<T>(json);
87
+ }
88
+ catch (Exception ex) when (ex is not InvalidOperationException)
89
+ {
90
+ throw new InvalidOperationException(
91
+ $"AOT/IL2CPP error cloning type '{type.FullName}'. " +
92
+ "Ensure the type is preserved and AOT-compatible.", ex);
93
+ }
94
+ }
95
+
96
+ /// Helper to clone individual items in collections, using the same logic as the main Clone method.
97
+ private static object CloneItem(object item)
98
+ {
99
+ if (item == null) return null;
100
+
101
+ var itemType = item.GetType();
102
+
103
+ // Value types are copied by value
104
+ if (itemType.IsValueType) return item;
105
+
106
+ // Strings are immutable
107
+ if (item is string) return item;
108
+
109
+ // ICloneable
110
+ if (item is ICloneable cloneable) return cloneable.Clone();
111
+
112
+ // UnityEngine.Object - return reference (can't deep clone ScriptableObjects easily)
113
+ if (item is Object) return item;
114
+
115
+ // Try JSON serialization for other serializable types
116
+ if (Attribute.IsDefined(itemType, typeof(SerializableAttribute)))
117
+ {
118
+ var json = JsonUtility.ToJson(item);
119
+ if (!string.IsNullOrEmpty(json) && json != "{}" && json != "null")
120
+ {
121
+ return JsonUtility.FromJson(json, itemType);
122
+ }
123
+ }
124
+
125
+ // Fallback: return the same reference
126
+ return item;
127
+ }
128
+ }
129
+ }
@@ -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.19",
4
+ "version": "1.0.22",
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",