/// <summary> /// If conditions have changed to allow finalizing creation of any pending attachment points, /// do it now. /// </summary> private void ProcessPendingAttachmentPoints() { if (CurrentFragmentId.IsKnown() && pendingAttachments.Count > 0) { // We have a valid destination fragment. Note that since this queue is in order of submission, // if an attachment point depends on a second attachment point for context, // that second will be either earlier in the list (because there was no valid current fragment when it was // created) or it will have a valid fragment. So by the time we get to the one with a dependency (pending.context != null), // its dependency will have a valid fragment id. int pendingCount = pendingAttachments.Count; for (int i = 0; i < pendingCount; ++i) { AttachmentPoint target = pendingAttachments[i].target; Vector3 frozenPosition = pendingAttachments[i].target.ObjectPosition; IAttachmentPoint context = pendingAttachments[i].context; SetupAttachmentPoint(plugin, target, context); FragmentId fragmentId = CurrentFragmentId; if (context != null) { fragmentId = context.FragmentId; } Debug.Assert(fragmentId.IsKnown(), $"FragmentId {fragmentId.FormatStr()} invalid from {(context != null ? "context" : "head")} in processing pending"); Fragment fragment = EnsureFragment(fragmentId); Debug.Assert(fragment != null, "Valid fragmentId but no fragment found"); fragment.AddAttachmentPoint(target); } // All pending must now be in a good home fragment, clear the to-do list. pendingAttachments.Clear(); } }
/// <summary> /// Teleport (as opposed to Move) means that the object is meant to have disappeared at its old position /// and instantaneously reappeared at its new position in frozen space without traversing the space in between. /// </summary> /// <remarks> /// This is equivalent to releasing the existing attachment point and creating a new one, /// except in that the attachment point reference remains valid. /// See <see cref="WorldLockingManager.TeleportAttachmentPoint"/>. /// </remarks> /// <param name="attachPointIface">The attachment point to teleport</param> /// <param name="newFrozenPosition">The position to teleport to.</param> /// <param name="context">The optional context.</param> public void TeleportAttachmentPoint(IAttachmentPoint attachPointIface, Vector3 newFrozenPosition, IAttachmentPoint context) { AttachmentPoint attachPoint = attachPointIface as AttachmentPoint; if (attachPoint != null) { attachPoint.ObjectPosition = newFrozenPosition; // Save the fragment it's currently in, in case it changes here. FragmentId oldFragmentId = attachPoint.FragmentId; // If it's not in a valid fragment, it is still pending and will get processed when the system is ready. if (oldFragmentId.IsKnown()) { FragmentId newFragmentId = GetTargetFragmentId(context); // If there is a valid current fragment, if (newFragmentId.IsKnown()) { // Fill it in with a new one. SetupAttachmentPoint(plugin, attachPoint, context); if (attachPoint.FragmentId != oldFragmentId) { ChangeAttachmentPointFragment(oldFragmentId, attachPoint); } } else { AddPendingAttachmentPoint(attachPoint, context); } } } }
/// <summary> /// Add a new attachment point to the pending list to be processed when the system is ready. /// </summary> /// <param name="attachPoint">Attachment point to process later.</param> /// <param name="context">Optional spawning attachment point, may be null.</param> private void AddPendingAttachmentPoint(AttachmentPoint attachPoint, IAttachmentPoint context) { // Flag as being in an invalid state attachPoint.HandleStateChange(AttachmentPointStateType.Pending); pendingAttachments.Add( new PendingAttachmentPoint { target = attachPoint, context = context } ); }
/// <summary> /// Add an existing attachment point to this fragment. /// </summary> /// <remarks> /// The attachment point might currently belong to another fragment, if /// it is being moved from the other to this. /// Since this is only used internally, it operates directly on an AttachmentPoint /// rather than an interface to avoid an unnecessary downcast. /// </remarks> /// <param name="attachPoint"></param> public void AddAttachmentPoint(AttachmentPoint attachPoint) { if (attachPoint != null) { if (attachPoint.StateHandler != null) { updateStateAllAttachments += attachPoint.StateHandler; } attachPoint.HandleStateChange(State); attachmentList.Add(attachPoint); } }
/// <summary> /// Helper to move an attachment point from one fragment to another. /// </summary> /// <remarks> /// Assumes that the attachment point's FragmentId property has already been set to the new fragment. /// </remarks> /// <param name="oldFragmentId">Source fragment</param> /// <param name="attachPoint">The attachment point</param> private void ChangeAttachmentPointFragment(FragmentId oldFragmentId, AttachmentPoint attachPoint) { Debug.Assert(oldFragmentId != attachPoint.FragmentId, "Moving attachment point from and to same fragment"); Fragment oldFragment = EnsureFragment(oldFragmentId); Fragment newFragment = EnsureFragment(attachPoint.FragmentId); Debug.Assert(oldFragment != null, "Valid fragmentId's but null source fragment"); Debug.Assert(newFragment != null, "Valid fragmentId's but null destination fragment"); // Add to the new fragment newFragment.AddAttachmentPoint(attachPoint); // Remove from the old fragment oldFragment.ReleaseAttachmentPoint(attachPoint); }
/// <summary> /// Absorb the contents of another fragment, emptying it. /// </summary> /// <param name="other">The fragment to lose all its contents to this.</param> public void AbsorbOtherFragment(Fragment other) { Debug.Assert(other != this, $"Trying to merge to and from the same fragment {FragmentId}"); int otherCount = other.attachmentList.Count; for (int i = 0; i < otherCount; ++i) { AttachmentPoint att = other.attachmentList[i]; att.Set(FragmentId, att.CachedPosition, att.AnchorId, att.LocationFromAnchor); if (att.StateHandler != null) { updateStateAllAttachments += att.StateHandler; } att.HandleStateChange(State); attachmentList.Add(att); } other.ReleaseAll(); }
/// <summary> /// Notify system attachment point is no longer needed. See <see cref="IAttachmentPointManager.ReleaseAttachmentPoint"/> /// </summary> /// <param name="attachmentPoint"></param> public void ReleaseAttachmentPoint(IAttachmentPoint attachmentPoint) { AttachmentPoint attachPoint = attachmentPoint as AttachmentPoint; if (attachPoint != null) { if (attachPoint.StateHandler != null) { updateStateAllAttachments -= attachPoint.StateHandler; } attachPoint.HandleStateChange(AttachmentPointStateType.Released); attachmentList.Remove(attachPoint); } else { Debug.LogError("On release, IAttachmentPoint isn't AttachmentPoint"); } }
/// <summary> /// Create and register a new attachment point. /// </summary> /// <remarks> /// The attachment point itself is a fairly opaque handle. Its effects are propagated to the client via the /// two handlers associated with it. /// The optional context attachment point provides an optional contextual hint to where in the anchor /// graph to bind the new attachment point. /// See <see cref="IAttachmentPointManager.CreateAttachmentPoint"/>. /// </remarks> /// <param name="frozenPosition">The position in the frozen space at which to start the attachment point</param> /// <param name="context">The optional context into which to create the attachment point (may be null)</param> /// <param name="locationHandler">Delegate to handle WorldLocking system adjustments to position</param> /// <param name="stateHandler">Delegate to handle WorldLocking connectivity changes</param> /// <returns>The new attachment point interface.</returns> public IAttachmentPoint CreateAttachmentPoint(Vector3 frozenPosition, IAttachmentPoint context, AdjustLocationDelegate locationHandler, AdjustStateDelegate stateHandler) { FragmentId fragmentId = GetTargetFragmentId(context); AttachmentPoint attachPoint = new AttachmentPoint(locationHandler, stateHandler); attachPoint.ObjectPosition = frozenPosition; if (fragmentId.IsKnown()) { SetupAttachmentPoint(plugin, attachPoint, context); Fragment fragment = EnsureFragment(fragmentId); Debug.Assert(fragment != null, "Valid fragmentId but no fragment found"); fragment.AddAttachmentPoint(attachPoint); } else { AddPendingAttachmentPoint(attachPoint, context); } return(attachPoint); }
/// <summary> /// Release an attachment point for disposal. The attachment point is no longer valid after this call. /// </summary> /// <remarks> /// In the unlikely circumstance that another attachment point has been spawned from this one /// but has not yet been processed (is still in the pending queue), /// that relationship is broken on release of this one, and when the other attachment point is /// finally processed, it will be as if it was created with a null context. /// </remarks> /// <param name="attachPointIface">The attachment point to release.</param> public void ReleaseAttachmentPoint(IAttachmentPoint attachPointIface) { AttachmentPoint attachPoint = attachPointIface as AttachmentPoint; if (attachPoint != null) { Fragment fragment = EnsureFragment(attachPoint.FragmentId); if (fragment != null) { // Fragment handles notification. fragment.ReleaseAttachmentPoint(attachPoint); } else { // Notify of the state change to released. attachPoint.HandleStateChange(AttachmentPointStateType.Released); // The list of pending attachments is expected to be small, and release of an attachment // point while there are pending attachments is expected to be rare. So brute force it here. // If the attachment point being released is a target in the pending list, remove it. // If it is the context of another pending target, set that context to null. // Proceed through the list in reverse order, because context fixes will only be found // later in the list than the original, and once the original is found we are done. int pendingCount = pendingAttachments.Count; for (int i = pendingCount - 1; i >= 0; --i) { if (pendingAttachments[i].context == attachPoint) { var p = pendingAttachments[i]; p.context = null; pendingAttachments[i] = p; } else if (pendingAttachments[i].target == attachPoint) { pendingAttachments.RemoveAt(i); break; } } } } }
/// <summary> /// Helper function for setting up the internals of an AttachmentPoint /// </summary> /// <param name="plugin">The global plugin</param> /// <param name="target">The attachment point to setup</param> /// <param name="context">The optional context <see cref="CreateAttachmentPoint"/></param> public static void SetupAttachmentPoint(IPlugin plugin, AttachmentPoint target, IAttachmentPoint context) { if (context != null) { AnchorId anchorId; Vector3 locationFromAnchor; plugin.CreateAttachmentPointFromSpawner(context.AnchorId, context.LocationFromAnchor, target.ObjectPosition, out anchorId, out locationFromAnchor); FragmentId fragmentId = context.FragmentId; target.Set(fragmentId, target.ObjectPosition, anchorId, locationFromAnchor); } else { FragmentId currentFragmentId = plugin.GetMostSignificantFragmentId(); AnchorId anchorId; Vector3 locationFromAnchor; plugin.CreateAttachmentPointFromHead(target.ObjectPosition, out anchorId, out locationFromAnchor); FragmentId fragmentId = currentFragmentId; target.Set(fragmentId, target.ObjectPosition, anchorId, locationFromAnchor); } }
/// <summary> /// Move (as opposed to Teleport) means that the object is meant to have traversed /// flozen space from its old position to the given new position on some continuous path. /// </summary> /// <remarks> /// Not to be used for automatic (i.e. FrozenWorld Engine instigated) moves. /// See <see cref="WorldLockingManager.MoveAttachmentPoint"/> /// </remarks> /// <param name="attachPoint">Attachment point to move</param> /// <param name="newFrozenPosition">The new position in frozen space</param> public void MoveAttachmentPoint(IAttachmentPoint attachPointIface, Vector3 newFrozenPosition) { AttachmentPoint attachPoint = attachPointIface as AttachmentPoint; if (attachPoint != null) { attachPoint.ObjectPosition = newFrozenPosition; // If it's not in a valid fragment, it is still pending and will get processed when the system is ready. if (attachPoint.FragmentId.IsKnown()) { float minDistToUpdateSq = 0.5f * 0.5f; float moveDistanceSq = (newFrozenPosition - attachPoint.CachedPosition).sqrMagnitude; if (moveDistanceSq > minDistToUpdateSq) { attachPoint.LocationFromAnchor = plugin.MoveAttachmentPoint(newFrozenPosition, attachPoint.AnchorId, attachPoint.LocationFromAnchor); attachPoint.CachedPosition = newFrozenPosition; } // Else we haven't moved enough to bother doing anything. } } }
/// <summary> /// Run through all attachment points, get their adjustments from the plugin and apply them. /// </summary> /// <remarks> /// This must be called between plugin.Refreeze() and plugin.RefreezeFinish(). /// </remarks> public void AdjustAll(IPlugin plugin) { int count = attachmentList.Count; for (int i = 0; i < count; ++i) { AttachmentPoint attach = attachmentList[i]; AnchorId newAnchorId; Vector3 newLocationFromAnchor; Pose adjustment; if (plugin.ComputeAttachmentPointAdjustment(attach.AnchorId, attach.LocationFromAnchor, out newAnchorId, out newLocationFromAnchor, out adjustment)) { attach.Set(FragmentId, attach.CachedPosition, newAnchorId, newLocationFromAnchor); attach.HandlePoseAdjustment(adjustment); } else { Debug.Log($"No adjustment during refreeze for {attach.AnchorId.ToString()}"); } } }