forked from MonoGame/MonoGame
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathContextScopeFactory.cs
218 lines (192 loc) · 8.71 KB
/
ContextScopeFactory.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
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
212
213
214
215
216
217
218
// MonoGame - Copyright (C) MonoGame Foundation, Inc
// This file is subject to the terms and conditions defined in
// file 'LICENSE.txt', which is part of this source code package.
using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Xna.Framework.Content.Pipeline;
using MonoGame.Framework.Content.Pipeline.Builder;
namespace MonoGame.Framework.Content
{
/// <summary>
/// The <see cref="IContentContext"/> represents some sort of context operation's context.
/// Usually, this represents a <see cref="ContentImporterContext"/>
/// or <see cref="ContentProcessorContext"/>.
///
/// However, because those types are part of the XNA namespace, they cannot be modified directly.
/// This interface is an adapter over those types.
/// </summary>
internal interface IContentContext : IDisposable
{
/// <inheritdoc cref="ContentImporterContext.IntermediateDirectory"/>
public string IntermediateDirectory { get; }
/// <inheritdoc cref="ContentImporterContext.Logger"/>
public ContentBuildLogger Logger { get; }
/// <inheritdoc cref="ContentProcessorContext.SourceIdentity"/>
public ContentIdentity SourceIdentity { get; }
}
/// <summary>
/// <para>
/// The <see cref="ContextScopeFactory"/> facilitates access to a <see cref="IContentContext"/>
/// instance without direct access to the actual context.
/// </para>
///
/// <para>
/// Anytime a content context operation is about to start, the operation should signal
/// the <see cref="BeginContext(IContentContext)"/> method.
/// <b> Critically </b>, the content operation must <b>dispose</b> the resulting context
/// when the operation has concluded.
/// </para>
///
/// <para>
/// The most recent content operation can be retrieved with the <see cref="ActiveContext"/>
/// property.
/// </para>
///
/// </summary>
internal static class ContextScopeFactory
{
private static AsyncLocal<List<IContentContext>> _contextStack = new AsyncLocal<List<IContentContext>>
{
Value = new List<IContentContext>(1)
};
private static AsyncLocal<IContentContext> _activeContext = new AsyncLocal<IContentContext>();
/// <summary>
/// Returns true when the <see cref="ActiveContext"/> is a valid <see cref="IContentContext"/> instance.
/// </summary>
public static bool HasActiveContext => _activeContext.Value != null;
/// <summary>
/// Access the latest <see cref="IContentContext"/> operation.
/// If no operations are running (and therefor there is no context), this
/// accessor will throw a <see cref="PipelineException"/>.
///
/// Use the <see cref="HasActiveContext"/> to check if there is an active context.
///
/// <para>
/// Each Task-chain may have its own unique ActiveContext, but if a task-chain
/// does not have an active context, then the parent task's active context will be used
/// recursively. This is the behaviour of AsyncLocal.
/// </para>
/// </summary>
/// <exception cref="PipelineException"></exception>
public static IContentContext ActiveContext
{
get
{
if (!HasActiveContext)
{
throw new PipelineException(
$"Cannot access {nameof(ActiveContext)} because there is no active context. Make sure that {nameof(ContextScopeFactory)}.{nameof(BeginContext)} has been called with the `using` keyword");
}
return _activeContext.Value;
}
}
/// <summary>
/// Start a <see cref="ContentProcessorContext"/> operation.
/// The <see cref="ContentProcessorContext"/> instance will be adapted into a
/// <see cref="IContentContext"/>
///
/// </summary>
/// <param name="context"></param>
/// <returns>
/// <b>this return value must be disposed when the context operation is complete!</b>
/// </returns>
public static IContentContext BeginContext(ContentProcessorContext context)
{
return BeginContext(new ContentProcessorContextAdapter(context));
}
/// <summary>
/// Start a <see cref="ContentImporterContext"/> operation.
/// The <see cref="ContentImporterContext"/> instance will be adapted into a
/// <see cref="IContentContext"/>
///
/// </summary>
/// <param name="context"></param>
/// <param name="evt">
/// A <see cref="ContentImporterContext"/> does not include a source file,
/// but the originating <see cref="PipelineBuildEvent"/> <i>does</i>. The event
/// will be used to fulfill the <see cref="IContentContext.SourceIdentity"/> value.
/// </param>
/// <returns>
/// <b>this return value must be disposed when the context operation is complete!</b>
/// </returns>
public static IContentContext BeginContext(ContentImporterContext context, PipelineBuildEvent evt)
{
return BeginContext(new ContentImporterContextAdapter(context, evt));
}
/// <summary>
/// Start a content context operation.
/// </summary>
/// <param name="scope"></param>
/// <returns>
/// <b>this return value must be disposed when the context operation is complete!</b>
/// </returns>
public static IContentContext BeginContext(IContentContext scope)
{
if (_contextStack.Value == null)
_contextStack.Value = new List<IContentContext>(1);
_contextStack.Value.Add(scope);
_activeContext.Value = scope;
return scope;
}
/// <summary>
/// The default implementation of the <see cref="IContentContext"/>
/// provides a basic Dispose() method that will remove the context
/// from the history in the <see cref="ContextScopeFactory"/>
/// </summary>
public abstract class ContextScope : IContentContext
{
public abstract string IntermediateDirectory { get; }
public abstract ContentBuildLogger Logger { get; }
public abstract ContentIdentity SourceIdentity { get; }
/// <summary>
/// Remove this context operation from the history in the <see cref="ContextScopeFactory"/>.
/// If this context was the <see cref="ContextScopeFactory.ActiveContext"/>, then this
/// method will reset the <see cref="ContextScopeFactory.ActiveContext"/> value to the next
/// most-recent context operation, or null if none exist.
/// </summary>
public virtual void Dispose()
{
_contextStack.Value.Remove(this);
// if someone else has already claimed the activeContext, then we don't need to care.
if (_activeContext.Value != this) return;
// either use the "most recent" (aka, last) context, or if the list is empty,
// there is no context.
_activeContext.Value = _contextStack.Value.Count > 0
? _contextStack.Value[^1]
: null;
}
}
private class ContentProcessorContextAdapter : ContextScope
{
private readonly ContentProcessorContext _context;
public ContentProcessorContextAdapter(ContentProcessorContext context)
{
_context = context;
}
public override string IntermediateDirectory => _context.IntermediateDirectory;
public override ContentBuildLogger Logger => _context.Logger;
public override ContentIdentity SourceIdentity => _context.SourceIdentity;
public override void Dispose()
{
base.Dispose();
if (_context is IDisposable disposable)
{
disposable.Dispose();
}
}
}
private class ContentImporterContextAdapter : ContextScope
{
private readonly ContentImporterContext _context;
public ContentImporterContextAdapter(ContentImporterContext context, PipelineBuildEvent evt)
{
_context = context;
SourceIdentity = new ContentIdentity(sourceFilename: evt.SourceFile);
}
public override string IntermediateDirectory => _context.IntermediateDirectory;
public override ContentBuildLogger Logger => _context.Logger;
public override ContentIdentity SourceIdentity { get; }
}
}
}