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
|
|
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
|
|
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
|
-
|
|
42
|
-
|
|
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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cz.xprees.variables",
|
|
3
3
|
"displayName": "Variables",
|
|
4
|
-
"version": "1.0.
|
|
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",
|