NodeCanvas Forums › Support › BUG (critical): ActionListPlayer deserialization
an ActionListPlayer referencing a global BB will crash on deserialization, because it tries to resolve the global BB reference from the deserialization callback.
Repo is simple – create a scene with a global BB and an ActionListPlayer with an action referring it, e.g. “set integer” with an integer from the global BB. Save, close, reload.
Help fixing this would be greatly appreciated, as the systems involved are quite complex (been sifting through the code for a couple of hours) and would likely take me a lot of time to figure it all out.
Thanks
Hey,
My bad sorry. That’s indeed an issue.
I have attached for you here a fixed version of ActionListPlayer.cs in a package.
Let me know if everything works correctly on your end as well.
Thanks!
Join us on Discord: https://discord.gg/97q2Rjh
It’s working, thanks.
Actually, there is still an issue, but it seems non-critical: in play mode, the first time the action that references the global variable plays the following pair of warnings:
Dynamic Parameter Variable ‘Global/myInteger’ Encountered…
Blackboard: Variable with name ‘myInteger’ already exists in blackboard ‘Global’. Returning existing instead of new.
Hello
You left the SendTaskOwnerDefaults call in OnValidate in the new version, which brings back the initial issue.
However, simply commenting it out leads to the missing task owner error.
I’ve added the [ExecuteInEditMode] attribute to the script, which causes Awake to be called, which in turn calls SendTaskOwnerDefaults.
Ran a few tests, seems to be working.
Could you please confirm if this is a proper fix?
Hello again,
I don’t really get any issues with the SendTaskOwnerDefaults being in OnValidate like this:
1 2 3 4 5 6 7 |
void OnValidate(){ if ( !Application.isPlaying && !UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode ){ SendTaskOwnerDefaults(); } } |
What problem are you getting?
Regardless, using [ExecuteInEditMode] to make Awake run in editor and thus SendTaskOwnerDefaults is also correct, yes.
Regarding the issue about Dynamic Variable encountered, I’ve attached for you here an improvement to the GlobalBlackboards.cs script which fixes this issue.
Let me know.
Thanks.
Join us on Discord: https://discord.gg/97q2Rjh
The repo is the same from the first post. The error only shows after loading the scene, not the first time creating the action list player and the global BB.
OnValidate() is also called during deserialization, and SendTaskOwnerDefaults cannot work at that time, as not even accessing the gameObject from the component is allowed at that time. Unfortunately, there is no way (at least that I could find) to determine if the currently executing code is during deserialization or not.
Using Unity 5.5, forgot to mention.
Hello again,
That is very weird. I am also on Unity 5.5 and tested exactly what you said. Having a task that references a global bb variable, saved the scene, closed unity, reopened it and loaded the scene. I get no errors at all.
OnValidate() is not really called during serialization as far as I know, otherwise there would be much more problems that this one in all of my projects 🙂
I’m sorry, but I really can’t reproduce the error you have here :/
Join us on Discord: https://discord.gg/97q2Rjh
Oops, I’ve made a mistake when merging your new version with the old one modified by me, the new one doesn’t have the OnValidate, it calls SendTaskOwnerDefaults directly from OnAfterDeserialize.
The end result is the same though, as it still uses API’s not usable at that time.
But it gets even weirder.
All of the following happens in a new project, only NodeCanvas imported (none of my changes), Unity 5.5.0f3.
https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnValidate.html
This function is called when the script is loaded or a value is changed in the inspector (Called in the editor only).
1 2 3 4 5 6 7 8 9 10 11 |
using UnityEngine; public class NewBehaviourScript : MonoBehaviour { void OnValidate() { Debug.Log("OnValidate"); } } |
New scene, new empty GO, add the script, save, new scene, open the previously saved scene.
On my machine, it prints out “OnValidate” every time. Does the same happen on your side?
New scene, new global BB and new ActionListPlayer. Add an integer to the BB, and an action to increment said integer in the ALP.
Save, new scene, open the previously saved scene.
And now the weird part.
If Android is set as the build target, I get these 2 errors:
FindObjectsOfType is not allowed to be called during serialization, call it from Awake or Start instead. Called from MonoBehaviour ‘ActionListPlayer’ on game object ‘ActionList’.
See “Script Serialization” page in the Unity Manual for further details.
UnityEngine.Object:FindObjectsOfType()
NodeCanvas.Framework.GlobalBlackboard:Find(String) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Variables/GlobalBlackboard.cs:42)
NodeCanvas.Framework.BBParameter:ResolveReference(IBlackboard, Boolean) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Variables/BBParameter.cs:270)
NodeCanvas.Framework.BBParameter:set_bb(IBlackboard) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Variables/BBParameter.cs:175)
NodeCanvas.Framework.BBParameter:SetBBFields(Object, IBlackboard) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Variables/BBParameter.cs:54)
NodeCanvas.Framework.Task:set_blackboard(IBlackboard) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Tasks/Task.cs:331)
NodeCanvas.Framework.Task:SetOwnerSystem(ITaskSystem) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Tasks/Task.cs:169)
NodeCanvas.ActionListPlayer:SendTaskOwnerDefaults() (at Assets/ParadoxNotion/NodeCanvas/Modules/ActionListPlayer/ActionListPlayer.cs:71)
NodeCanvas.ActionListPlayer:set_actionList(ActionList) (at Assets/ParadoxNotion/NodeCanvas/Modules/ActionListPlayer/ActionListPlayer.cs:38)
NodeCanvas.ActionListPlayer:OnAfterDeserialize() (at Assets/ParadoxNotion/NodeCanvas/Modules/ActionListPlayer/ActionListPlayer.cs:28)
UnityException: FindObjectsOfType is not allowed to be called during serialization, call it from Awake or Start instead. Called from MonoBehaviour ‘ActionListPlayer’ on game object ‘ActionList’.
See “Script Serialization” page in the Unity Manual for further details.
UnityEngine.Object.FindObjectsOfType[GlobalBlackboard] () (at C:/buildslave/unity/build/Runtime/Export/UnityEngineObject.cs:227)
NodeCanvas.Framework.GlobalBlackboard.Find (System.String name) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Variables/GlobalBlackboard.cs:42)
NodeCanvas.Framework.BBParameter.ResolveReference (IBlackboard targetBlackboard, Boolean useID) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Variables/BBParameter.cs:270)
NodeCanvas.Framework.BBParameter.set_bb (IBlackboard value) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Variables/BBParameter.cs:175)
NodeCanvas.Framework.BBParameter.SetBBFields (System.Object o, IBlackboard bb) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Variables/BBParameter.cs:54)
NodeCanvas.Framework.Task.set_blackboard (IBlackboard value) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Tasks/Task.cs:331)
NodeCanvas.Framework.Task.SetOwnerSystem (ITaskSystem newOwnerSystem) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Tasks/Task.cs:169)
NodeCanvas.ActionListPlayer.SendTaskOwnerDefaults () (at Assets/ParadoxNotion/NodeCanvas/Modules/ActionListPlayer/ActionListPlayer.cs:71)
NodeCanvas.ActionListPlayer.set_actionList (NodeCanvas.Framework.ActionList value) (at Assets/ParadoxNotion/NodeCanvas/Modules/ActionListPlayer/ActionListPlayer.cs:38)
NodeCanvas.ActionListPlayer.OnAfterDeserialize () (at Assets/ParadoxNotion/NodeCanvas/Modules/ActionListPlayer/ActionListPlayer.cs:28)
switch platform to PC, Mac & Linux Standalone, and now the errors when opening the scene change to:
get_isPlaying is not allowed to be called during serialization, call it from Awake or Start instead. Called from MonoBehaviour ‘ActionListPlayer’ on game object ‘ActionList’.
See “Script Serialization” page in the Unity Manual for further details.
UnityEngine.Application:get_isPlaying()
NodeCanvas.Framework.BBParameter:set_bb(IBlackboard) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Variables/BBParameter.cs:162)
NodeCanvas.Framework.BBParameter:SetBBFields(Object, IBlackboard) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Variables/BBParameter.cs:54)
NodeCanvas.Framework.Task:set_blackboard(IBlackboard) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Tasks/Task.cs:331)
NodeCanvas.Framework.Task:SetOwnerSystem(ITaskSystem) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Tasks/Task.cs:169)
NodeCanvas.ActionListPlayer:SendTaskOwnerDefaults() (at Assets/ParadoxNotion/NodeCanvas/Modules/ActionListPlayer/ActionListPlayer.cs:71)
NodeCanvas.ActionListPlayer:set_actionList(ActionList) (at Assets/ParadoxNotion/NodeCanvas/Modules/ActionListPlayer/ActionListPlayer.cs:38)
NodeCanvas.ActionListPlayer:OnAfterDeserialize() (at Assets/ParadoxNotion/NodeCanvas/Modules/ActionListPlayer/ActionListPlayer.cs:28)
UnityException: get_isPlaying is not allowed to be called during serialization, call it from Awake or Start instead. Called from MonoBehaviour ‘ActionListPlayer’ on game object ‘ActionList’.
See “Script Serialization” page in the Unity Manual for further details.
NodeCanvas.Framework.BBParameter.set_bb (IBlackboard value) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Variables/BBParameter.cs:162)
NodeCanvas.Framework.BBParameter.SetBBFields (System.Object o, IBlackboard bb) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Variables/BBParameter.cs:54)
NodeCanvas.Framework.Task.set_blackboard (IBlackboard value) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Tasks/Task.cs:331)
NodeCanvas.Framework.Task.SetOwnerSystem (ITaskSystem newOwnerSystem) (at Assets/ParadoxNotion/NodeCanvas/Framework/Runtime/Tasks/Task.cs:169)
NodeCanvas.ActionListPlayer.SendTaskOwnerDefaults () (at Assets/ParadoxNotion/NodeCanvas/Modules/ActionListPlayer/ActionListPlayer.cs:71)
NodeCanvas.ActionListPlayer.set_actionList (NodeCanvas.Framework.ActionList value) (at Assets/ParadoxNotion/NodeCanvas/Modules/ActionListPlayer/ActionListPlayer.cs:38)
NodeCanvas.ActionListPlayer.OnAfterDeserialize () (at Assets/ParadoxNotion/NodeCanvas/Modules/ActionListPlayer/ActionListPlayer.cs:28)
Copied your new version and made it ExecuteInEditMode, moving the SendTaskOwnerDefaults to Awake (if _actionList not null) and deserializing into _actionList seems to make it work.
So, I am completely lost now as of which version of ActionListPlayer.cs and GlobalBlackboard.cs you have in the end 🙂
I am attaching the local modified versions I have for both here just to confirm.
These two work fine for me here. Can you confirm the same?
Thanks.
Join us on Discord: https://discord.gg/97q2Rjh
All of the following happens in a new project, only NodeCanvas imported (none of my changes), Unity 5.5.0f3.
Could you also test the OnValidate thing?
Hey,
OnValidate is called every time a scene is loaded (once), yes. Is that what you mean to test? 🙂
Join us on Discord: https://discord.gg/97q2Rjh
Yes, that – since you said “OnValidate() is not really called during serialization as far as I know”.
Other than that, have you had the time to look at the error?
The following is my current modified version (based on 2.6.2), and it seems to work.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
using UnityEngine; using System.Collections.Generic; using NodeCanvas.Framework; using ParadoxNotion.Serialization; namespace NodeCanvas{ [AddComponentMenu("NodeCanvas/Action List")] [ExecuteInEditMode] public class ActionListPlayer : MonoBehaviour, ITaskSystem, ISerializationCallbackReceiver { [System.NonSerialized] private ActionList _actionList; [SerializeField] private Blackboard _blackboard; [SerializeField] private List<Object> _objectReferences; [SerializeField] private string _serializedList; public void OnBeforeSerialize(){ _objectReferences = new List<Object>(); _serializedList = JSONSerializer.Serialize(typeof(ActionList), actionList, false, _objectReferences); } public void OnAfterDeserialize(){ _actionList = JSONSerializer.Deserialize<ActionList>(_serializedList, _objectReferences); if (_actionList == null) _actionList = (ActionList)Task.Create(typeof(ActionList), this); } void Awake() { if (_actionList != null) SendTaskOwnerDefaults(); } //////// //////// public ActionList actionList{ get {return _actionList;} set {_actionList = value; SendTaskOwnerDefaults();} } public Component agent{ get {return this;} } public IBlackboard blackboard{ get {return _blackboard;} set { if ( !ReferenceEquals(_blackboard, value) ){ _blackboard = (Blackboard)(object)value; SendTaskOwnerDefaults(); } } } public float elapsedTime{ get {return actionList.elapsedTime;} } public Object contextObject{ get {return this;} } public static ActionListPlayer Create(){ return new GameObject("ActionList").AddComponent<ActionListPlayer>(); } public void SendTaskOwnerDefaults(){ actionList.SetOwnerSystem(this); foreach(var a in actionList.actions){ a.SetOwnerSystem(this); } } void ITaskSystem.SendEvent(ParadoxNotion.EventData eventData){ Debug.LogWarning("Sending events to action lists has no effect"); } [ContextMenu("Play")] public void Play(){ if (!Application.isPlaying) return; Play(this, this.blackboard, null); } public void Play(System.Action<bool> OnFinish){ Play(this, this.blackboard, OnFinish); } public void Play(Component agent, IBlackboard blackboard, System.Action<bool> OnFinish){ actionList.ExecuteAction(agent, blackboard, OnFinish); } public Status ExecuteAction() { return actionList.ExecuteAction(this, blackboard); } //////////////////////////////////////// ///////////GUI AND EDITOR STUFF///////// //////////////////////////////////////// #if UNITY_EDITOR void Reset(){ var bb = GetComponent<Blackboard>(); _blackboard = bb != null? bb : gameObject.AddComponent<Blackboard>(); _actionList = (ActionList)Task.Create(typeof(ActionList), this); } #endif } } |
Hey,
I am using Unity 5.5 and I still don’t get any errors at all by using SendTaskOwnerDefaults() in OnValidate. The problem with doing this in Awake with ExecuteInEditMode as you do above, is that Awake will not be called after a re-compile and as such this will result in “Owner System Is Null” for the action list. OnValidate though is called after a re-compile.
Join us on Discord: https://discord.gg/97q2Rjh
Yeah, I’ve noticed that null owner system too.
My current version. Seems to be working now. No idea why, probably stuff from the new version got mixed up with the old one 😀
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
using UnityEngine; using System.Collections.Generic; using NodeCanvas.Framework; using ParadoxNotion.Serialization; namespace NodeCanvas{ [AddComponentMenu("NodeCanvas/Action List")] public class ActionListPlayer : MonoBehaviour, ITaskSystem, ISerializationCallbackReceiver { [System.NonSerialized] private ActionList _actionList; [SerializeField] private Blackboard _blackboard; [SerializeField] private List<Object> _objectReferences; [SerializeField] private string _serializedList; public void OnBeforeSerialize(){ _objectReferences = new List<Object>(); _serializedList = JSONSerializer.Serialize(typeof(ActionList), _actionList, false, _objectReferences); } public void OnAfterDeserialize(){ _actionList = JSONSerializer.Deserialize<ActionList>(_serializedList, _objectReferences); if (_actionList == null) _actionList = (ActionList)Task.Create(typeof(ActionList), this); } void Awake() { if (_actionList != null) SendTaskOwnerDefaults(); } //////// //////// public ActionList actionList{ get {return _actionList;} set {_actionList = value; SendTaskOwnerDefaults();} } public Component agent{ get {return this;} } public IBlackboard blackboard{ get {return _blackboard;} set { if ( !ReferenceEquals(_blackboard, value) ){ _blackboard = (Blackboard)(object)value; SendTaskOwnerDefaults(); } } } public float elapsedTime{ get {return actionList.elapsedTime;} } public Object contextObject{ get {return this;} } public static ActionListPlayer Create(){ return new GameObject("ActionList").AddComponent<ActionListPlayer>(); } public void SendTaskOwnerDefaults(){ actionList.SetOwnerSystem(this); foreach(var a in actionList.actions){ a.SetOwnerSystem(this); } } void ITaskSystem.SendEvent(ParadoxNotion.EventData eventData){ Debug.LogWarning("Sending events to action lists has no effect"); } [ContextMenu("Play")] public void Play(){ if (!Application.isPlaying) return; Play(this, this.blackboard, null); } public void Play(System.Action<bool> OnFinish){ Play(this, this.blackboard, OnFinish); } public void Play(Component agent, IBlackboard blackboard, System.Action<bool> OnFinish){ actionList.ExecuteAction(agent, blackboard, OnFinish); } public Status ExecuteAction() { return actionList.ExecuteAction(this, blackboard); } //////////////////////////////////////// ///////////GUI AND EDITOR STUFF///////// //////////////////////////////////////// #if UNITY_EDITOR void Reset(){ var bb = GetComponent<Blackboard>(); _blackboard = bb != null? bb : gameObject.AddComponent<Blackboard>(); _actionList = (ActionList)Task.Create(typeof(ActionList), this); } void OnValidate() { if (!Application.isPlaying && !UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) SendTaskOwnerDefaults(); } #endif } } |