public static InvalidCatchupException ApplyCatchupInfo(this SceneTree tree, Dictionary data) { const string errorPrefix = "Catchup info target entry is malformed and was ignored: "; Debug.Assert(_catchupPhase == CatchupApplicationPhase.NotCatchingUp); // Deliver catchup "RPCs" _catchupPhase = CatchupApplicationPhase.ApplyingInfo; foreach (var pathRaw in data.Keys) { // Parse path if (!(pathRaw is NodePath path)) { GD.PushWarning(errorPrefix + "key was not a node path."); continue; } GD.Print($"Catching up node at \"{path}\""); var node = tree.Root.GetNodeOrNull <IRequiresCatchup>(path); if (node == null) { GD.PushWarning(errorPrefix + $"specified node does not exist or does not implement {nameof(IRequiresCatchup)}."); continue; } if (!((Node)node).IsInGroup(GroupRequiresCatchup)) { GD.PushWarning(errorPrefix + "specified node at is not flagged as currently requiring catchup."); continue; } // Attempt to apply catchup try { node.HandleCatchupState(data[pathRaw]); ((Node)node).RemoveFromGroup(GroupRequiresCatchup); } catch (InvalidCatchupException e) { e.Instigator = (Node)node; _catchupPhase = CatchupApplicationPhase.NotCatchingUp; return(e); } } // Ensure that all nodes requiring catchup have been caught up if (tree.DoesAnythingRequireCatchup()) { _catchupPhase = CatchupApplicationPhase.NotCatchingUp; var list = new StringBuilder(); var first = true; foreach (var node in tree.GetNodesInGroup(GroupRequiresCatchup).Cast <Node>()) { if (!first) { list.Append(","); } first = false; list.Append(node.GetPath()); } return(new InvalidCatchupException($"At least one node marked as currently requiring catchup has not been caught up.\nNodes: {list}")); } // Perform macro catchup validation _catchupPhase = CatchupApplicationPhase.SecondPass; foreach (var(node, validator) in tree.EnumerateGroupMembers <IMacroCatchupValidator>(GroupMacroCatchupValidator)) { try { validator.ValidateCatchupState(); } catch (InvalidCatchupException e) { e.Instigator = node; _catchupPhase = CatchupApplicationPhase.NotCatchingUp; return(e); } node.RemoveFromGroup(GroupMacroCatchupValidator); } // Announce catchup finish foreach (var(node, awaiter) in tree.EnumerateGroupMembers <ICatchupAwaiter>(GroupCatchupAwaiter)) { awaiter._CaughtUp(); node.RemoveFromGroup(GroupCatchupAwaiter); } _catchupPhase = CatchupApplicationPhase.NotCatchingUp; return(null); }