forked from Unity-Technologies/UnityCsReference
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDataWatchService.cs
More file actions
212 lines (185 loc) · 6.84 KB
/
Copy pathDataWatchService.cs
File metadata and controls
212 lines (185 loc) · 6.84 KB
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Experimental.UIElements;
using Object = UnityEngine.Object;
namespace UnityEditor
{
class DataWatchHandle : IDataWatchHandle
{
public readonly int id;
public WeakReference service;
public Object watched { get; private set; }
public DataWatchHandle(int id, DataWatchService service, Object watched)
{
this.id = id;
this.service = new WeakReference(service);
this.watched = watched;
}
public bool disposed
{
get
{
return ReferenceEquals(watched, null);
}
}
public void Dispose()
{
if (disposed)
{
throw new InvalidOperationException("DataWatchHandle was already disposed of");
}
if (service != null && service.IsAlive)
{
(service.Target as DataWatchService).RemoveWatch(this);
}
service = null;
watched = null;
}
}
internal class DataWatchService : IDataWatchService
{
public static DataWatchService sharedInstance = new DataWatchService();
private HashSet<Object> m_DirtySet = new HashSet<Object>();
struct Spy
{
public readonly int handleID;
public readonly VisualElement watcher;
public readonly Action onDataChanged;
public Spy(int handleID, VisualElement watcher, Action onDataChanged)
{
this.handleID = handleID;
this.watcher = watcher;
this.onDataChanged = onDataChanged;
}
}
struct Watchers
{
public List<Spy> spyList;
public ChangeTrackerHandle tracker;
}
private Dictionary<int, DataWatchHandle> m_Handles = new Dictionary<int, DataWatchHandle>();
private Dictionary<Object, Watchers> m_Watched = new Dictionary<Object, Watchers>();
public DataWatchService()
{
// TODO probably just do this when panel becomes active and stop when being inactive
Undo.postprocessModifications += PostProcessUndo;
}
~DataWatchService()
{
Undo.postprocessModifications -= PostProcessUndo;
}
// callback to watch undo stack for changes
// TODO: add the hook before animation strips some changes in record mode
public UndoPropertyModification[] PostProcessUndo(UndoPropertyModification[] modifications)
{
foreach (var m in modifications)
{
var cv = m.currentValue;
if (cv == null || cv.target == null)
continue;
if (m_Watched.ContainsKey(cv.target))
{
m_DirtySet.Add(cv.target);
}
}
return modifications;
}
// go through all trackers and poll their native revisions
public void PollNativeData()
{
foreach (var w in m_Watched)
{
// Unity Object can be destroyed without us knowing
// They will compare to null but the C# object is still valid
// Other wise we check the object for changes
if (w.Key == null || w.Value.tracker.PollForChanges())
{
m_DirtySet.Add(w.Key);
}
}
}
public void FlushDirtySet(HashSet<Object> dirty)
{
dirty.UnionWith(m_DirtySet);
m_DirtySet.Clear();
}
public void ProcessNotificationQueue(BaseVisualElementPanel panel, HashSet<Object> dirtySet)
{
// since callbacks when being invoked we make copies for safe iteration
var tmpSpys = new List<Spy>();
// get the set of Dirties from C++ and clear it.
foreach (var o in dirtySet)
{
Watchers watchers;
if (m_Watched.TryGetValue(o, out watchers))
{
tmpSpys.Clear();
tmpSpys.AddRange(watchers.spyList);
foreach (Spy spy in tmpSpys)
{
if (spy.watcher.panel == panel)
{
// for any watches trigger callbacks
spy.onDataChanged();
}
else if (spy.watcher.panel == null)
{
Debug.Log("Leaking Data Spies from element: " + spy.watcher);
}
}
}
}
}
static int s_WatchID;
public IDataWatchHandle AddWatch(VisualElement watcher, Object watched, Action onDataChanged)
{
if (watched == null)
throw new ArgumentException("Object watched cannot be null");
DataWatchHandle handle = new DataWatchHandle(++s_WatchID, this, watched);
m_Handles[handle.id] = handle;
Watchers watchers;
if (!m_Watched.TryGetValue(watched, out watchers))
{
watchers = new Watchers
{
spyList = new List<Spy>(),
tracker = ChangeTrackerHandle.AcquireTracker(watched),
};
m_Watched[watched] = watchers;
}
watchers.spyList.Add(new Spy(handle.id, watcher, onDataChanged));
return handle;
}
public void RemoveWatch(IDataWatchHandle handle)
{
DataWatchHandle handleImpl = (DataWatchHandle)handle;
if (m_Handles.Remove(handleImpl.id))
{
Watchers watchers;
if (m_Watched.TryGetValue(handleImpl.watched, out watchers))
{
List<Spy> spyList = watchers.spyList;
for (int i = 0; i < spyList.Count; i++)
{
Spy spy = spyList[i];
if (spy.handleID == handleImpl.id)
{
spyList.RemoveAt(i);
if (watchers.spyList.Count == 0)
{
watchers.tracker.ReleaseTracker();
m_Watched.Remove(handleImpl.watched);
}
return;
}
}
}
}
throw new ArgumentException("Data watch was not registered");
}
}
}