Skip to content

Commit 6c38855

Browse files
committed
feat: Reworked SwapSystem
- Added ISwappable - TeleportableObject and TeleportableObjectClone now have OnSwap event - Other system now subscribes to OnSwap event of instances instead of GlobalEvents.OnSwap (drastically improves performance) - Merged ComponentUtility and SwapUtility - SwapUtility now uses HashSet for faster lookup
1 parent b977120 commit 6c38855

15 files changed

+4003
-3832
lines changed

Assets/MainScene.unity

Lines changed: 3796 additions & 3766 deletions
Large diffs are not rendered by default.

Assets/Scripts/Common/Utility/ComponentUtility.cs

Lines changed: 0 additions & 19 deletions
This file was deleted.

Assets/Scripts/Common/Utility/ComponentUtility.cs.meta

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using PerceptionVR.Portals;
2+
using UnityEngine;
3+
4+
namespace PerceptionVR.Extensions
5+
{
6+
public static class ComponentExtensions
7+
{
8+
public static bool TryGetComponentInParent<T>(this Component component, out T result)
9+
{
10+
result = component.GetComponentInParent<T>();
11+
return result != null;
12+
}
13+
14+
public static bool TryGetComponentInChildren<T>(this Component component, out T result)
15+
{
16+
result = component.GetComponentInChildren<T>();
17+
return result != null;
18+
}
19+
}
20+
}

Assets/Scripts/Extensions/ComponentExtensions.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/Scripts/Physics/Colliders/ColliderGroup.cs

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using UnityEngine;
77
using PerceptionVR.Common.Generic;
88
using PerceptionVR.Debug;
9+
using PerceptionVR.Extensions;
910
using PerceptionVR.Portals;
1011

1112
namespace PerceptionVR.Physics
@@ -16,6 +17,8 @@ public class ColliderGroup : ObservableCollection<Collider>
1617
public enum FilterMode { Include, Exclude }
1718

1819
[HideInInspector] public string debugName = "";
20+
21+
private Dictionary<ISwappable, List<Collider>> swappableColliders = new();
1922

2023
public void SetFilter(FilterMode mode, IEnumerable<ColliderGroup> filterGroups)
2124
{
@@ -26,10 +29,44 @@ public void SetFilter(FilterMode mode, IEnumerable<ColliderGroup> filterGroups)
2629
//ComponentTracking.allColliders.OnRemoved += RemoveRange;
2730

2831
// Swap colliders on swap event
29-
GlobalEvents.OnSwap += swapData => SwapUtility.PerformSwap(this, swapData.colliderSwaps);
32+
//GlobalEvents.OnSwap += swapData => SwapUtility.PerformSwap(this, swapData.colliderSwaps);
3033

31-
// Un-ignore added colliders with rest of this group
32-
OnAdded += colliders => CollisionMatrix.SetCollisions(this, colliders, true);
34+
35+
OnAdded += colliders =>
36+
{
37+
// Un-ignore added colliders with rest of this group
38+
CollisionMatrix.SetCollisions(this, colliders, true);
39+
40+
// Register swap event
41+
foreach (var collider in colliders)
42+
{
43+
if (collider.TryGetComponentInParent(out ISwappable swappable))
44+
{
45+
if (swappableColliders.TryGetValue(swappable, out var colliderList))
46+
colliderList.Add(collider);
47+
else
48+
{
49+
swappableColliders.Add(swappable, new List<Collider> {collider});
50+
swappable.OnSwap += PerformSwap;
51+
}
52+
}
53+
}
54+
};
55+
56+
OnRemoved += colliders =>
57+
{
58+
// Unregister swap event
59+
foreach (var collider in colliders)
60+
if (collider.TryGetComponentInParent(out ISwappable swappable) && swappableColliders.TryGetValue(swappable, out var colliderList))
61+
{
62+
colliderList.Remove(collider);
63+
if (colliderList.Count == 0)
64+
{
65+
swappableColliders.Remove(swappable);
66+
swappable.OnSwap -= PerformSwap;
67+
}
68+
}
69+
};
3370

3471
switch (mode)
3572
{
@@ -91,5 +128,7 @@ public override IEnumerator<Collider> GetEnumerator()
91128
collection.RemoveAll(x => x == null);
92129
return base.GetEnumerator();
93130
}
131+
132+
private void PerformSwap(SwapData swapData) => SwapUtility.PerformSwap(this, swapData.colliderSwaps);
94133
}
95134
}
Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System.Collections.Generic;
22
using PerceptionVR.Portals;
33
using PerceptionVR.Debug;
4-
using PerceptionVR.Global;
4+
using PerceptionVR.Extensions;
55
using UnityEngine;
66

77
namespace PerceptionVR.Physics
@@ -10,24 +10,12 @@ public class SubscribableSwapTrigger : SubscribableTrigger
1010
{
1111
private readonly List<Collider> shouldSwapIn = new();
1212
private readonly List<Collider> shouldSwapOut = new();
13+
private readonly Dictionary<ISwappable, List<Collider>> swappableCollidersInside = new();
1314

1415
protected override void Awake()
1516
{
1617
base.Awake();
17-
GlobalEvents.OnSwap += swapData =>
18-
{
19-
if(collidersInside.Count == 0)
20-
return;
21-
var applicableSwaps = SwapUtility.GetApplicableSwaps(collidersInside, swapData.colliderSwaps);
22-
foreach (var applicableSwap in applicableSwaps)
23-
{
24-
collidersInside.Remove(applicableSwap.toRemove);
25-
collidersInside.Add(applicableSwap.toAdd);
26-
shouldSwapOut.Add(applicableSwap.toRemove);
27-
shouldSwapIn.Add(applicableSwap.toAdd);
28-
}
29-
};
30-
18+
3119
OnBeforeProcessQueue += () =>
3220
{
3321
if (shouldSwapIn.Count != 0)
@@ -51,20 +39,77 @@ protected override void Awake()
5139
}
5240
shouldSwapOut.Clear();
5341
}
54-
5542
};
5643
}
5744

5845
protected override void OnTriggerEnter(Collider other)
5946
{
60-
if(!shouldSwapIn.Remove(other)) // Confirm swap in
61-
base.OnTriggerEnter(other); // Or do standard OnTriggerEnter
47+
// Try confirm swap in
48+
if (!shouldSwapIn.Remove(other))
49+
{
50+
// Register swappable
51+
TryRegisterSwappable(other);
52+
53+
// Do standard OnTriggerEnter
54+
base.OnTriggerEnter(other);
55+
}
56+
6257
}
6358

6459
protected override void OnTriggerExit(Collider other)
6560
{
66-
if(!shouldSwapOut.Remove(other)) // Confirm swap out
67-
base.OnTriggerExit(other); // Or do standard OnTriggerExit
61+
// Try confirm swap out
62+
if (!shouldSwapOut.Remove(other))
63+
{
64+
// Unregister swappable
65+
TryUnregisterSwappable(other);
66+
67+
// Do standard OnTriggerExit
68+
base.OnTriggerExit(other);
69+
}
70+
71+
}
72+
73+
private void PerformSwap(SwapData swapData)
74+
{
75+
SwapUtility.PerformSwap(collidersInside, swapData.colliderSwaps, out var appliedSwaps);
76+
foreach (var applicableSwap in appliedSwaps)
77+
{
78+
// Note:
79+
// I already have ISwappable in swapData.
80+
// If slow => Make "RegisterSwappable" without GetComponentInParent
81+
TryUnregisterSwappable(applicableSwap.removed);
82+
TryRegisterSwappable(applicableSwap.added);
83+
shouldSwapOut.Add(applicableSwap.removed);
84+
shouldSwapIn.Add(applicableSwap.added);
85+
}
86+
}
87+
88+
private void TryRegisterSwappable(Collider other)
89+
{
90+
if (other.TryGetComponentInParent(out ISwappable swappable))
91+
{
92+
if(swappableCollidersInside.TryGetValue(swappable, out var colliders))
93+
colliders.Add(other);
94+
else
95+
{
96+
swappableCollidersInside.Add(swappable, new List<Collider> {other});
97+
swappable.OnSwap += PerformSwap;
98+
}
99+
}
100+
}
101+
102+
private void TryUnregisterSwappable(Collider other)
103+
{
104+
if (other.TryGetComponentInParent(out ISwappable swappable) && swappableCollidersInside.TryGetValue(swappable, out var colliders))
105+
{
106+
colliders.Remove(other);
107+
if (colliders.Count == 0)
108+
{
109+
swappableCollidersInside.Remove(swappable);
110+
swappable.OnSwap -= PerformSwap;
111+
}
112+
}
68113
}
69114
}
70115
}

Assets/Scripts/Portal/Clone/ComponentClones/TeleportableObjectClone.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88

99
namespace PerceptionVR.Portals
1010
{
11-
public class TeleportableObjectClone : TrackedCloneBase<TeleportableObject>
11+
public class TeleportableObjectClone : TrackedCloneBase<TeleportableObject>, ISwappable
1212
{
13-
public event Action<TeleportData> OnTeleport;
13+
public event Action<TeleportData> OnTeleport;
14+
15+
// Return currentTarget.OnSwap
16+
public Action<SwapData> OnSwap { get; set; }
1417

1518
private PhysicsObjectClone physicsObjectClone;
1619
private LocalTransformClone[] transformClones;
@@ -70,5 +73,6 @@ private void OnTargetTeleportCallback(TeleportData data)
7073
}
7174

7275
private void OnDestroy() => currentTarget.OnTeleport -= OnTargetTeleportCallback;
76+
7377
}
7478
}

Assets/Scripts/Portal/Clone/PortalCloneSystem.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,13 @@ private void OnTeleportCallback(TeleportData teleportData)
155155

156156
// Notify swap
157157
GlobalEvents.OnSwap?.Invoke(swapData);
158+
159+
// Create copy of OnSwap event
160+
var ntOnSwap = (Action<SwapData>)nt.teleportableObject.OnSwap.Clone();
161+
var ntcOnSwap = (Action<SwapData>)nt.cloneTeleportableObject.OnSwap.Clone();
162+
163+
ntOnSwap?.Invoke(swapData);
164+
ntcOnSwap?.Invoke(swapData);
158165

159166
//Debugger.LogInfo($"Swapping teleportables of {swapData.teleportableSwap.Item1} and {swapData.teleportableSwap.Item2} in {this}");
160167

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
using PerceptionVR.Common;
3+
4+
namespace PerceptionVR.Portals
5+
{
6+
public interface ISwappable : IMonoBehaviour
7+
{
8+
public Action<SwapData> OnSwap { get; set; }
9+
}
10+
}

Assets/Scripts/Portal/SwapSystem/ISwappable.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/Scripts/Portal/SwapSystem/SwapData.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ namespace PerceptionVR.Portals
66
{
77
public struct SwapData
88
{
9-
public readonly (TeleportableObject, TeleportableObjectClone) teleportableSwap;
9+
public readonly (ISwappable, ISwappable) rootSwap;
1010
public readonly IEnumerable<(Collider, Collider)> colliderSwaps;
1111
public readonly IEnumerable<(Renderer, Renderer)> rendererSwaps;
1212

13-
public SwapData(TeleportableObject teleportableObject, TeleportableObjectClone clone)
13+
public SwapData(ISwappable original, ISwappable clone)
1414
{
15-
teleportableSwap = (teleportableObject, clone);
16-
colliderSwaps = ComponentUtility.CreateComponentTuple<Collider, Collider>(teleportableObject.transform, clone.transform);
17-
rendererSwaps = ComponentUtility.CreateComponentTuple<Renderer, Renderer>(teleportableObject.transform, clone.transform);
15+
rootSwap = (original, clone);
16+
colliderSwaps = SwapUtility.CreateSwaps<Collider, Collider>(original.transform, clone.transform);
17+
rendererSwaps = SwapUtility.CreateSwaps<Renderer, Renderer>(original.transform, clone.transform);
1818
}
1919
}
2020
}
Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,63 @@
11
using System.Collections.Generic;
2+
using System.Linq;
3+
using PerceptionVR.Debug;
4+
using UnityEngine;
25

36
namespace PerceptionVR.Portals
47
{
58
public static class SwapUtility
69
{
7-
// Swap every item in the tuple which is in the collection with second tuple value
8-
public static void PerformSwap<T>(ICollection<T> collection, IEnumerable<(T,T)> swaps) => PerformSwap(collection, swaps, out _);
9-
public static void PerformSwap<T>(ICollection<T> collection, IEnumerable<(T,T)> swaps, out IEnumerable<(T removed, T added)> appliedSwaps)
10+
// Swap every item in collection with tuple (Item1 -> Item2, Item2 -> Item1)
11+
public static void PerformSwap<T>(ICollection<T> collection, IEnumerable<(T, T)> swaps)
1012
{
11-
appliedSwaps = GetApplicableSwaps(collection, swaps);
13+
var set = new HashSet<T>(collection);
14+
foreach (var (a, b) in swaps)
15+
{
16+
if (set.Contains(a))
17+
{
18+
collection.Remove(a);
19+
collection.Add(b);
20+
}
21+
else if (set.Contains(b))
22+
{
23+
collection.Remove(b);
24+
collection.Add(a);
25+
}
26+
}
27+
}
28+
29+
public static void PerformSwap<T>(ICollection<T> collection, IEnumerable<(T, T)> swaps, out IEnumerable<(T removed, T added)> appliedSwaps)
30+
{
31+
appliedSwaps = GetApplicableSwaps(collection, swaps).ToList();
32+
1233
foreach (var (toRemove, toAdd) in appliedSwaps)
1334
{
1435
collection.Remove(toRemove);
1536
collection.Add(toAdd);
1637
}
1738
}
1839

19-
public static IEnumerable<(T toRemove, T toAdd)> GetApplicableSwaps<T>(ICollection<T> collection, IEnumerable<(T,T)> swaps)
40+
public static IEnumerable<(T toRemove, T toAdd)> GetApplicableSwaps<T>(ICollection<T> collection, IEnumerable<(T, T)> swaps)
2041
{
21-
var applicable = new List<(T, T)>();
42+
var set = new HashSet<T>(collection);
2243
foreach (var (a, b) in swaps)
2344
{
24-
if (collection.Contains(a))
25-
applicable.Add((a, b));
26-
else if (collection.Contains(b))
27-
applicable.Add((b, a));
45+
if (set.Contains(a))
46+
yield return (a, b);
47+
else if (set.Contains(b))
48+
yield return (b, a);
2849
}
29-
return applicable;
50+
}
51+
52+
// Create list of associated component tuples
53+
// Clone object must be cloned from original object
54+
public static IEnumerable<(TOriginal original, TClone clone)> CreateSwaps<TOriginal, TClone>(Component originalRoot, Component cloneRoot)
55+
{
56+
var originalComponents = originalRoot.GetComponentsInChildren<TOriginal>();
57+
var cloneComponents = cloneRoot.GetComponentsInChildren<TClone>();
58+
if(originalComponents.Length != cloneComponents.Length)
59+
Debugger.LogError($"{originalRoot} and {cloneRoot} have different numbers of {typeof(TOriginal)} and {typeof(TClone)} components");
60+
return originalComponents.Zip(cloneComponents, (original, clone) => (original, clone));
3061
}
3162
}
3263
}

0 commit comments

Comments
 (0)