-
Notifications
You must be signed in to change notification settings - Fork 2.5k
/
Copy pathUnitySynchronizationContext.cs
169 lines (148 loc) · 6 KB
/
UnitySynchronizationContext.cs
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
// 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 System.Diagnostics;
using System.Threading;
using UnityEngine.Scripting;
namespace UnityEngine
{
internal sealed class UnitySynchronizationContext : SynchronizationContext
{
private const int kAwqInitialCapacity = 20;
private readonly List<WorkRequest> m_AsyncWorkQueue;
private readonly List<WorkRequest> m_CurrentFrameWork = new List<WorkRequest>(kAwqInitialCapacity);
private readonly int m_MainThreadID;
internal int MainThreadId => m_MainThreadID;
private int m_TrackedCount = 0;
private UnitySynchronizationContext(int mainThreadID)
{
m_AsyncWorkQueue = new List<WorkRequest>(kAwqInitialCapacity);
m_MainThreadID = mainThreadID;
}
private UnitySynchronizationContext(List<WorkRequest> queue, int mainThreadID)
{
m_AsyncWorkQueue = queue;
m_MainThreadID = mainThreadID;
}
// Send will process the call synchronously. If the call is processed on the main thread, we'll invoke it
// directly here. If the call is processed on another thread it will be queued up like POST to be executed
// on the main thread and it will wait. Once the main thread processes the work we can continue
public override void Send(SendOrPostCallback callback, object state)
{
if (m_MainThreadID == System.Threading.Thread.CurrentThread.ManagedThreadId)
{
callback(state);
}
else
{
using (var waitHandle = new ManualResetEvent(false))
{
lock (m_AsyncWorkQueue)
{
m_AsyncWorkQueue.Add(new WorkRequest(callback, state, waitHandle));
}
waitHandle.WaitOne();
}
}
}
public override void OperationStarted() { Interlocked.Increment(ref m_TrackedCount); }
public override void OperationCompleted() { Interlocked.Decrement(ref m_TrackedCount); }
// Post will add the call to a task list to be executed later on the main thread then work will continue asynchronously
public override void Post(SendOrPostCallback callback, object state)
{
lock (m_AsyncWorkQueue)
{
m_AsyncWorkQueue.Add(new WorkRequest(callback, state));
}
}
// CreateCopy returns a new UnitySynchronizationContext object, but the queue is still shared with the original
public override SynchronizationContext CreateCopy()
{
return new UnitySynchronizationContext(m_AsyncWorkQueue, m_MainThreadID);
}
// Exec will execute tasks off the task list
public void Exec()
{
lock (m_AsyncWorkQueue)
{
m_CurrentFrameWork.AddRange(m_AsyncWorkQueue);
m_AsyncWorkQueue.Clear();
}
// When you invoke work, remove it from the list to stop it being triggered again (case 1213602)
while (m_CurrentFrameWork.Count > 0)
{
WorkRequest work = m_CurrentFrameWork[0];
m_CurrentFrameWork.RemoveAt(0);
work.Invoke();
}
}
private bool HasPendingTasks()
{
return m_AsyncWorkQueue.Count != 0 || m_TrackedCount != 0;
}
// SynchronizationContext must be set before any user code is executed. This is done on
// Initial domain load and domain reload at MonoManager ReloadAssembly
[RequiredByNativeCode]
private static void InitializeSynchronizationContext()
{
var synchronizationContext = new UnitySynchronizationContext(System.Threading.Thread.CurrentThread.ManagedThreadId);
SynchronizationContext.SetSynchronizationContext(synchronizationContext);
Awaitable.SetSynchronizationContext(synchronizationContext);
}
// All requests must be processed on the main thread where the full Unity API is available
// See ScriptRunDelayedTasks in PlayerLoopCallbacks.h
[RequiredByNativeCode]
private static void ExecuteTasks()
{
var context = SynchronizationContext.Current as UnitySynchronizationContext;
if (context != null)
context.Exec();
}
[RequiredByNativeCode]
private static bool ExecutePendingTasks(long millisecondsTimeout)
{
var context = SynchronizationContext.Current as UnitySynchronizationContext;
if (context == null)
{
return true;
}
var stopwatch = new Stopwatch();
stopwatch.Start();
while (context.HasPendingTasks())
{
if (stopwatch.ElapsedMilliseconds > millisecondsTimeout)
{
break;
}
context.Exec();
Thread.Sleep(1);
}
return !context.HasPendingTasks();
}
private struct WorkRequest
{
private readonly SendOrPostCallback m_DelagateCallback;
private readonly object m_DelagateState;
private readonly ManualResetEvent m_WaitHandle;
public WorkRequest(SendOrPostCallback callback, object state, ManualResetEvent waitHandle = null)
{
m_DelagateCallback = callback;
m_DelagateState = state;
m_WaitHandle = waitHandle;
}
public void Invoke()
{
try
{
m_DelagateCallback(m_DelagateState);
}
finally
{
m_WaitHandle?.Set();
}
}
}
}
}