• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zorbathut / dec / 11064119160

27 Sep 2024 03:40AM UTC coverage: 90.69% (-0.02%) from 90.707%
11064119160

push

github

zorbathut
Change over the class dependency system to attributes.

4549 of 5016 relevant lines covered (90.69%)

192374.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.79
/src/ParserModular.cs
1
namespace Dec
2
{
3
    using System;
4
    using System.Collections.Generic;
5
    using System.IO;
6
    using System.Linq;
7
    using System.Reflection;
8
    using System.Runtime.CompilerServices;
9
    using System.Xml.Linq;
10

11
    /// <summary>
12
    /// Handles all parsing and initialization of dec structures.
13
    ///
14
    /// Intended for moddable games; use Parser for non-moddable or prototype games.
15
    /// </summary>
16
    public class ParserModular
17
    {
18
        public class Module : IParser
19
        {
20
            internal string name;
21
            internal readonly List<ReaderFileDec> readers = new List<ReaderFileDec>();
13,250✔
22
            internal Recorder.IUserSettings userSettings;
23

24
            internal Module(Recorder.IUserSettings userSettings)
13,250✔
25
            {
13,250✔
26
                this.userSettings = userSettings;
13,250✔
27
            }
13,250✔
28

29
            /// <summary>
30
            /// Pass a directory in for recursive processing.
31
            /// </summary>
32
            /// <remarks>
33
            /// This function will ignore dot-prefixed directory names and files, which are common for development tools to create.
34
            /// </remarks>
35
            /// <param name="directory">The directory to look for files in.</param>
36
            public void AddDirectory(string directory)
37
            {
185✔
38
                if (directory.IsNullOrEmpty())
185✔
39
                {
40✔
40
                    Dbg.Err("Attempted to add a null or empty directory to the parser; this is probably wrong");
40✔
41
                    return;
40✔
42
                }
43

44
                foreach (var file in Directory.GetFiles(directory, "*.xml"))
965✔
45
                {
265✔
46
                    if (!System.IO.Path.GetFileName(file).StartsWith("."))
265✔
47
                    {
205✔
48
                        AddFile(Parser.FileType.Xml, file);
205✔
49
                    }
205✔
50
                }
265✔
51

52
                foreach (var subdir in Directory.GetDirectories(directory))
620✔
53
                {
100✔
54
                    if (!System.IO.Path.GetFileName(subdir).StartsWith("."))
100✔
55
                    {
40✔
56
                        AddDirectory(subdir);
40✔
57
                    }
40✔
58
                }
100✔
59
            }
185✔
60

61
            /// <summary>
62
            /// Pass a file in for processing.
63
            /// </summary>
64
            /// <param name="stringName">A human-readable identifier useful for debugging. Generally, the name of the file that the string was read from. Not required; will be derived from filename automatically.</param>
65
            public void AddFile(Parser.FileType fileType, string filename, string identifier = null)
66
            {
285✔
67
                if (filename.IsNullOrEmpty())
285✔
68
                {
40✔
69
                    Dbg.Err("Attempted to add a null or empty filename to the parser; this is probably wrong");
40✔
70
                    return;
40✔
71
                }
72

73
                if (identifier == null)
245✔
74
                {
245✔
75
                    // This is imperfect, but good enough. People can pass their own identifier in if they want something clever.
76
                    identifier = Path.GetFileName(filename);
245✔
77
                }
245✔
78

79
                using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
245✔
80
                {
245✔
81
                    AddStream(fileType, fs, identifier);
245✔
82
                }
245✔
83
            }
285✔
84

85
            /// <summary>
86
            /// Pass a stream in for processing.
87
            /// </summary>
88
            /// <param name="identifier">A human-readable identifier useful for debugging. Generally, the name of the file that the stream was built from. Not required; will be derived from filename automatically</param>
89
            public void AddStream(Parser.FileType fileType, Stream stream, string identifier = "(unnamed)")
90
            {
265✔
91
                if (stream == null)
265✔
92
                {
20✔
93
                    Dbg.Err("Attempted to add a null stream to the parser; this is probably wrong");
20✔
94
                    return;
20✔
95
                }
96

97
                using (var reader = new StreamReader(stream))
245✔
98
                {
245✔
99
                    AddTextReader(fileType, reader, identifier);
245✔
100
                }
245✔
101
            }
265✔
102

103
            /// <summary>
104
            /// Pass a string in for processing.
105
            /// </summary>
106
            /// <param name="identifier">A human-readable identifier useful for debugging. Generally, the name of the file that the string was built from. Not required, but helpful.</param>
107
            public void AddString(Parser.FileType fileType, string contents, string identifier = "(unnamed)")
108
            {
12,385✔
109
                if (contents.IsNullOrEmpty())
12,385✔
110
                {
60✔
111
                    Dbg.Err("Attempted to add a null or empty string to the parser; this is probably wrong");
60✔
112
                    return;
60✔
113
                }
114

115
                // This is a really easy error to make; we might as well handle it.
116
                if (contents.EndsWith(".xml"))
12,325✔
117
                {
20✔
118
                    Dbg.Err($"It looks like you've passed the filename `{contents}` to AddString instead of the actual XML file. Either use AddFile() or pass the file contents in.");
20✔
119
                }
20✔
120

121
                using (var reader = new StringReader(contents))
12,325✔
122
                {
12,325✔
123
                    AddTextReader(fileType, reader, identifier);
12,325✔
124
                }
12,325✔
125
            }
12,385✔
126

127
            private void AddTextReader(Parser.FileType fileType, TextReader textReader, string identifier = "(unnamed)")
128
            {
12,570✔
129
                if (s_Status != Status.Accumulating)
12,570✔
130
                {
5✔
131
                    Dbg.Err($"Adding data while while the world is in {s_Status} state; should be {Status.Accumulating} state");
5✔
132
                }
5✔
133

134
                string bakedIdentifier = (name == "core") ? identifier : $"{name}:{identifier}";
12,570✔
135

136
                ReaderFileDec reader;
137

138
                if (fileType == Parser.FileType.Xml)
12,570✔
139
                {
12,570✔
140
                    reader = ReaderFileDecXml.Create(textReader, bakedIdentifier, userSettings);
12,570✔
141
                }
12,570✔
142
                else
143
                {
×
144
                    Dbg.Err($"{bakedIdentifier}: Only XML files are supported at this time");
×
145
                    return;
×
146
                }
147

148
                if (reader != null)
12,570✔
149
                {
12,510✔
150
                    readers.Add(reader);
12,510✔
151
                }
12,510✔
152

153
                // otherwise, error has already been printed
154
            }
12,570✔
155
        }
156

157
        // Global status
158
        private enum Status
159
        {
160
            Uninitialized,
161
            Accumulating,
162
            Processing,
163
            Distributing,
164
            Finalizing,
165
            Finished,
166
        }
167
        private static Status s_Status = Status.Uninitialized;
168

169
        // Data stored from initialization parameters
170
        private List<Type> staticReferences = new List<Type>();
12,470✔
171
        private Recorder.IUserSettings userSettings;
172

173
        // Modules
174
        internal List<Module> modules = new List<Module>();
12,470✔
175

176
        // Used for static reference validation
177
        private static Action s_StaticReferenceHandler = null;
178

179
        /// <summary>
180
        /// Creates a Parser.
181
        /// </summary>
182
        public ParserModular(Recorder.IUserSettings userSettings = null)
12,470✔
183
        {
12,470✔
184
            this.userSettings = userSettings;
12,470✔
185

186
            if (s_Status != Status.Uninitialized)
12,470✔
187
            {
10✔
188
                Dbg.Err($"Parser created while the world is in {s_Status} state; should be {Status.Uninitialized} state");
10✔
189
            }
10✔
190
            s_Status = Status.Accumulating;
12,470✔
191

192
            bool unitTestMode = Config.TestParameters != null;
12,470✔
193

194
            {
12,470✔
195
                IEnumerable<Type> staticRefs;
196
                if (!unitTestMode)
12,470✔
197
                {
5✔
198
                    staticRefs = UtilReflection.GetAllUserTypes().Where(t => t.GetCustomAttribute<StaticReferencesAttribute>() != null);
6,800✔
199
                }
5✔
200
                else if (Config.TestParameters.explicitStaticRefs != null)
12,465✔
201
                {
535✔
202
                    staticRefs = Config.TestParameters.explicitStaticRefs;
535✔
203
                }
535✔
204
                else
205
                {
11,930✔
206
                    staticRefs = Enumerable.Empty<Type>();
11,930✔
207
                }
11,930✔
208

209
                foreach (var type in staticRefs)
38,170✔
210
                {
380✔
211
                    if (type.GetCustomAttribute<StaticReferencesAttribute>() == null)
380✔
212
                    {
30✔
213
                        Dbg.Err($"{type} is not tagged as StaticReferences");
30✔
214
                    }
30✔
215

216
                    if (!type.IsAbstract || !type.IsSealed)
380✔
217
                    {
30✔
218
                        Dbg.Err($"{type} is not static");
30✔
219
                    }
30✔
220

221
                    staticReferences.Add(type);
380✔
222
                }
380✔
223
            }
12,470✔
224

225
            Serialization.Initialize();
12,470✔
226
        }
12,470✔
227

228
        /// <summary>
229
        /// Creates and registers a new module with a given name.
230
        /// </summary>
231
        public Module CreateModule(string name)
232
        {
13,250✔
233
            var module = new Module(userSettings);
13,250✔
234
            module.name = name;
13,250✔
235

236
            if (modules.Any(mod => mod.name == name))
14,145✔
237
            {
20✔
238
                Dbg.Err($"Created duplicate module {name}");
20✔
239
            }
20✔
240

241
            modules.Add(module);
13,250✔
242
            return module;
13,250✔
243
        }
13,250✔
244

245
        /// <summary>
246
        /// Finish all parsing.
247
        /// </summary>
248
        /// <remarks>
249
        /// The `dependencies` parameter can be used to feed in dependencies for the PostLoad function.
250
        /// This is a placeholder and is probably going to be replaced at some point, though only with something more capable.
251
        /// </remarks>
252
        public void Finish()
253
        {
12,455✔
254
            using (var _ = new CultureInfoScope(Config.CultureInfo))
12,455✔
255
            {
12,455✔
256
                if (s_Status != Status.Accumulating)
12,455✔
257
                {
5✔
258
                    Dbg.Err($"Finishing while the world is in {s_Status} state; should be {Status.Accumulating} state");
5✔
259
                }
5✔
260
                s_Status = Status.Processing;
12,455✔
261

262
                var readerContext = new ReaderContext() { allowReflection = true, allowRefs = false };
12,455✔
263

264
                // Collate reader decs
265
                var registeredDecs = new Dictionary<(Type, string), List<ReaderFileDec.ReaderDec>>();
12,455✔
266
                foreach (var module in modules)
63,835✔
267
                {
13,235✔
268
                    var seenDecs = new Dictionary<(Type, string), ReaderNode>();
13,235✔
269
                    foreach (var reader in module.readers)
64,715✔
270
                    {
12,505✔
271
                        foreach (var readerDec in reader.ParseDecs())
130,255✔
272
                        {
46,370✔
273
                            var id = (readerDec.type.GetDecRootType(), readerDec.name);
46,370✔
274

275
                            var collidingDec = seenDecs.TryGetValue(id);
46,370✔
276
                            if (collidingDec != null)
46,370✔
277
                            {
100✔
278
                                Dbg.Err($"{collidingDec.GetInputContext()} / {readerDec.node.GetInputContext()}: Dec [{id.Item1}:{id.name}] defined twice");
100✔
279

280
                                // If the already-parsed one is abstract, we throw it away and go with the non-abstract one, because it's arguably more likely to be the one the user wants.
281
                                if (!(registeredDecs[id].Select(dec => dec.abstrct).LastOrDefault(abstrct => abstrct.HasValue) ?? false))
300✔
282
                                {
60✔
283
                                    continue;
60✔
284
                                }
285

286
                                registeredDecs.Remove(id);
40✔
287
                            }
40✔
288

289
                            seenDecs[id] = readerDec.node;
46,310✔
290

291
                            if (!registeredDecs.TryGetValue(id, out var list))
46,310✔
292
                            {
45,190✔
293
                                list = new List<ReaderFileDec.ReaderDec>();
45,190✔
294
                                registeredDecs[id] = list;
45,190✔
295
                            }
45,190✔
296
                            list.Add(readerDec);
46,310✔
297
                        }
46,310✔
298
                    }
12,505✔
299
                }
13,235✔
300

301
                // Compile reader decs into Order assemblies
302
                var registeredDecOrders = new Dictionary<(Type, string), List<ReaderFileDec.ReaderDec>>();
12,455✔
303
                foreach (var seenDec in registeredDecs)
127,665✔
304
                {
45,150✔
305
                    var orders = Serialization.CompileDecOrders(seenDec.Value);
45,150✔
306

307
                    // If we have no orders, this probably ended up deleted.
308
                    if (orders.Count > 0)
45,150✔
309
                    {
44,810✔
310
                        registeredDecOrders[seenDec.Key] = orders;
44,810✔
311
                    }
44,810✔
312
                }
45,150✔
313

314
                // Instantiate all decs
315
                var toParseDecOrders = new Dictionary<(Type, string), List<ReaderFileDec.ReaderDec>>();
12,455✔
316
                foreach (var (id, orders) in registeredDecOrders)
126,985✔
317
                {
44,810✔
318
                    // It's currently sort of unclear how we should be deriving the type.
319
                    // I'm choosing, for now, to go with "the most derived type in the list, assuming all types are in the same inheritance sequence".
320
                    // Thankfully this isn't too hard to do.
321
                    var typeDeterminor = orders[0];
44,810✔
322
                    bool abstrct = typeDeterminor.abstrct ?? false;
44,810✔
323

324
                    foreach (var order in orders.Skip(1))
135,360✔
325
                    {
480✔
326
                        // Since we're iterating over this anyway, yank the abstract updates out as go.
327
                        if (order.abstrct.HasValue)
480✔
328
                        {
80✔
329
                            abstrct = order.abstrct.Value;
80✔
330
                        }
80✔
331

332
                        if (order.type == typeDeterminor.type)
480✔
333
                        {
320✔
334
                            // fast case
335
                            continue;
320✔
336
                        }
337

338
                        if (order.type.IsSubclassOf(typeDeterminor.type))
160✔
339
                        {
100✔
340
                            typeDeterminor = order;
100✔
341
                            continue;
100✔
342
                        }
343

344
                        if (typeDeterminor.type.IsSubclassOf(order.type))
60✔
345
                        {
40✔
346
                            continue;
40✔
347
                        }
348

349
                        // oops, they're not subclasses of each other
350
                        Dbg.Err($"{typeDeterminor.inputContext} / {order.inputContext}: Modded dec with tree-identifier [{id.Item1}:{id.Item2}] has conflicting types without a simple subclass relationship ({typeDeterminor.type}/{order.type}); deferring to {order.type}");
20✔
351
                        typeDeterminor = order;
20✔
352
                    }
20✔
353

354
                    // We don't actually want an instance of this.
355
                    if (abstrct)
44,810✔
356
                    {
740✔
357
                        continue;
740✔
358
                    }
359

360
                    // Not an abstract dec instance, so create our instance
361
                    var decInstance = (Dec)typeDeterminor.type.CreateInstanceSafe("dec", typeDeterminor.node);
44,070✔
362

363
                    // Error reporting happens within CreateInstanceSafe; if we get null out, we just need to clean up elegantly
364
                    if (decInstance != null)
44,070✔
365
                    {
44,030✔
366
                        decInstance.DecName = id.Item2;
44,030✔
367

368
                        Database.Register(decInstance);
44,030✔
369

370
                        // create a new map that filters out abstract objects and failed instantiations
371
                        toParseDecOrders.Add(id, orders);
44,030✔
372
                    }
44,030✔
373
                }
44,070✔
374

375
                // It's time to actually pull references out of the database, so let's stop spitting out empty warnings.
376
                Database.SuppressEmptyWarning();
12,455✔
377

378
                foreach (var (id, orders) in toParseDecOrders)
125,425✔
379
                {
44,030✔
380
                    // Accumulate our orders
381
                    var completeOrders = orders;
44,030✔
382

383
                    var currentOrder = orders;
44,030✔
384
                    while (true)
45,050✔
385
                    {
45,050✔
386
                        // See if we have a parent
387
                        var decWithParent = currentOrder.Where(dec => dec.parent != null).LastOrDefault();
90,760✔
388
                        if (decWithParent.parent == null || decWithParent.parent == "")
45,050✔
389
                        {
43,970✔
390
                            break;
43,970✔
391
                        }
392

393
                        var parentId = (id.Item1, decWithParent.parent);
1,080✔
394
                        if (!registeredDecOrders.TryGetValue(parentId, out var parentDec))
1,080✔
395
                        {
60✔
396
                            Dbg.Err($"{decWithParent.node.GetInputContext()}: Dec [{decWithParent.type}:{id.Item2}] is attempting to use parent `[{parentId.Item1}:{parentId.parent}]`, but no such dec exists");
60✔
397
                            // guess we'll just try to build it from nothing
398
                            break;
60✔
399
                        }
400

401
                        completeOrders.InsertRange(0, parentDec);
1,020✔
402

403
                        currentOrder = parentDec;
1,020✔
404
                    }
1,020✔
405

406
                    var targetDec = Database.Get(id.Item1, id.Item2);
44,030✔
407
                    Serialization.ParseElement(completeOrders.Select(order => order.node).ToList(), targetDec.GetType(), targetDec, readerContext, new Recorder.Context(), isRootDec: true, ordersOverride: completeOrders.Select(order => (Serialization.ParseCommand.Patch, order.node)).ToList());
135,450✔
408
                }
44,030✔
409

410
                if (s_Status != Status.Processing)
12,455✔
411
                {
×
412
                    Dbg.Err($"Distributing while the world is in {s_Status} state; should be {Status.Processing} state");
×
413
                }
×
414
                s_Status = Status.Distributing;
12,455✔
415

416
                foreach (var stat in staticReferences)
38,125✔
417
                {
380✔
418
                    if (!StaticReferencesAttribute.StaticReferencesFilled.Contains(stat))
380✔
419
                    {
100✔
420
                        s_StaticReferenceHandler = () =>
100✔
421
                        {
160✔
422
                            s_StaticReferenceHandler = null;
160✔
423
                            StaticReferencesAttribute.StaticReferencesFilled.Add(stat);
160✔
424
                        };
160✔
425
                    }
100✔
426

427
                    bool touched = false;
380✔
428
                    foreach (var field in stat.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static))
2,600✔
429
                    {
730✔
430
                        var dec = Database.Get(field.FieldType, field.Name);
730✔
431
                        if (dec == null)
730✔
432
                        {
30✔
433
                            Dbg.Err($"Static reference class {stat} has member `{field.FieldType} {field.Name}` that does not correspond to any loaded Dec");
30✔
434
                            field.SetValue(null, null); // this is unnecessary, but it does kick the static constructor just in case we wouldn't do it otherwise
30✔
435
                        }
30✔
436
                        else if (!field.FieldType.IsAssignableFrom(dec.GetType()))
700✔
437
                        {
30✔
438
                            Dbg.Err($"Static reference class {stat} has member `{field.FieldType} {field.Name}` that is not compatible with actual {dec.GetType()} {dec}");
30✔
439
                            field.SetValue(null, null); // this is unnecessary, but it does kick the static constructor just in case we wouldn't do it otherwise
30✔
440
                        }
30✔
441
                        else
442
                        {
670✔
443
                            field.SetValue(null, dec);
670✔
444
                        }
670✔
445

446
                        touched = true;
730✔
447
                    }
730✔
448

449
                    if (s_StaticReferenceHandler != null)
380✔
450
                    {
40✔
451
                        if (touched)
40✔
452
                        {
35✔
453
                            // Otherwise we shouldn't even expect this to have been registered, but at least there's literally no fields in it so it doesn't matter
454
                            Dbg.Err($"Failed to properly register {stat}; you may be missing a call to Dec.StaticReferencesAttribute.Initialized() in its static constructor, or the class may already have been initialized elsewhere (this should have thrown an error)");
35✔
455
                        }
35✔
456

457
                        s_StaticReferenceHandler = null;
40✔
458
                    }
40✔
459
                }
380✔
460

461
                if (s_Status != Status.Distributing)
12,455✔
462
                {
×
463
                    Dbg.Err($"Finalizing while the world is in {s_Status} state; should be {Status.Distributing} state");
×
464
                }
×
465
                s_Status = Status.Finalizing;
12,455✔
466

467
                // figure out our config/postload order; first we just run over all the Decs and collate classes
468
                var decTypes = new Dictionary<Type, List<Dec>>();
12,455✔
469
                foreach (var dec in Database.List)
125,425✔
470
                {
44,030✔
471
                    var list = decTypes.TryGetValue(dec.GetType());
44,030✔
472
                    if (list == null)
44,030✔
473
                    {
12,725✔
474
                        list = new List<Dec>();
12,725✔
475
                        decTypes[dec.GetType()] = list;
12,725✔
476
                    }
12,725✔
477

478
                    list.Add(dec);
44,030✔
479
                }
44,030✔
480

481
                List<Dag<Type>.Dependency> postLoadDependencies = new List<Dag<Type>.Dependency>();
12,455✔
482
                foreach (var type in decTypes)
62,815✔
483
                {
12,725✔
484
                    foreach (var rsaa in type.Key.GetCustomAttributes<SetupDependsOnAttribute>())
38,300✔
485
                    {
70✔
486
                        var dependsOn = rsaa.Type;
70✔
487

488
                        // make sure it inherits from Dec.Dec
489
                        if (!dependsOn.IsSubclassOf(typeof(Dec)))
70✔
490
                        {
×
491
                            Dbg.Err($"{type.Key} has a SetupDependsOnAttribute on {dependsOn}, but {dependsOn} is not a Dec type");
×
492
                            continue;
×
493
                        }
494

495
                        if (!decTypes.ContainsKey(rsaa.Type))
70✔
496
                        {
5✔
497
                            Dbg.Err($"{type.Key} has a SetupDependsOnAttribute on {dependsOn}, but {dependsOn} is not a known Dec type; this might result in weird behavior, either create an instance of {dependsOn} or pester ZorbaTHut on Discord if you need this fixed");
5✔
498
                            continue;
5✔
499
                        }
500

501
                        postLoadDependencies.Add(new Dag<Type>.Dependency { before = rsaa.Type, after = type.Key });
65✔
502
                    }
65✔
503
                }
12,725✔
504

505
                var postprocessOrder = Dag<Type>.CalculateOrder(decTypes.Keys, postLoadDependencies, t => t.Name);
25,180✔
506

507
                foreach (var type in postprocessOrder)
62,815✔
508
                {
12,725✔
509
                    foreach (var dec in decTypes[type])
126,235✔
510
                    {
44,030✔
511
                        try
512
                        {
44,030✔
513
                            dec.ConfigErrors(err => Dbg.Err($"{dec}: {err}"));
44,080✔
514
                        }
43,970✔
515
                        catch (Exception e)
60✔
516
                        {
60✔
517
                            Dbg.Ex(e);
60✔
518
                        }
60✔
519
                    }
44,030✔
520
                }
12,725✔
521

522
                foreach (var type in postprocessOrder)
62,815✔
523
                {
12,725✔
524
                    foreach (var dec in decTypes[type])
126,235✔
525
                    {
44,030✔
526
                        try
527
                        {
44,030✔
528
                            dec.PostLoad(err => Dbg.Err($"{dec}: {err}"));
44,080✔
529
                        }
43,970✔
530
                        catch (Exception e)
60✔
531
                        {
60✔
532
                            Dbg.Ex(e);
60✔
533
                        }
60✔
534
                    }
44,030✔
535
                }
12,725✔
536

537
                if (s_Status != Status.Finalizing)
12,455✔
538
                {
×
539
                    Dbg.Err($"Completing while the world is in {s_Status} state; should be {Status.Finalizing} state");
×
540
                }
×
541
                s_Status = Status.Finished;
12,455✔
542
            }
12,455✔
543
        }
12,455✔
544

545
        internal static void Clear()
546
        {
26,295✔
547
            if (s_Status != Status.Finished && s_Status != Status.Uninitialized)
26,295✔
548
            {
15✔
549
                Dbg.Err($"Clearing while the world is in {s_Status} state; should be {Status.Uninitialized} state or {Status.Finished} state");
15✔
550
            }
15✔
551
            s_Status = Status.Uninitialized;
26,295✔
552
        }
26,295✔
553

554
        [MethodImpl(MethodImplOptions.NoInlining)]
555
        internal static void StaticReferencesInitialized()
556
        {
80✔
557
            if (s_StaticReferenceHandler != null)
80✔
558
            {
60✔
559
                s_StaticReferenceHandler();
60✔
560
                return;
60✔
561
            }
562

563
            Dbg.Err($"Initializing static reference class at an inappropriate time. Either you forgot to add [StaticReferences] to the class, or you accessed it before it was ready.");
20✔
564
        }
80✔
565
    }
566
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc