tgstation-server 6.12.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
EngineManager.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Linq;
5using System.Net.Http;
6using System.Text;
7using System.Threading;
8using System.Threading.Tasks;
9
10using Microsoft.Extensions.Logging;
11
18
20{
23 {
27 const string VersionFileName = "Version.txt";
28
32 const string ActiveVersionFileName = "ActiveVersion.txt";
33
35 public EngineVersion? ActiveVersion { get; private set; }
36
38 public IReadOnlyList<EngineVersion> InstalledVersions
39 {
40 get
41 {
43 return installedVersions.Keys.ToList();
44 }
45 }
46
51
56
61
65 readonly ILogger<EngineManager> logger;
66
70 readonly Dictionary<EngineVersion, ReferenceCountingContainer<IEngineInstallation, EngineExecutableLock>> installedVersions;
71
75 readonly SemaphoreSlim changeDeleteSemaphore;
76
80 volatile TaskCompletionSource activeVersionChanged;
81
87 {
88 ArgumentNullException.ThrowIfNull(version);
89
90 if (!version.Engine.HasValue)
91 throw new InvalidOperationException("version.Engine cannot be null!");
92
93 if (version.CustomIteration == 0)
94 throw new InvalidOperationException("version.CustomIteration cannot be 0!");
95 }
96
105 {
106 this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
107 this.engineInstaller = engineInstaller ?? throw new ArgumentNullException(nameof(engineInstaller));
108 this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
109 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
110
111 installedVersions = new Dictionary<EngineVersion, ReferenceCountingContainer<IEngineInstallation, EngineExecutableLock>>();
112 changeDeleteSemaphore = new SemaphoreSlim(1);
113 activeVersionChanged = new TaskCompletionSource();
114 }
115
117 public void Dispose() => changeDeleteSemaphore.Dispose();
118
120 public async ValueTask ChangeVersion(
121 JobProgressReporter progressReporter,
122 EngineVersion version,
123 Stream? customVersionStream,
124 bool allowInstallation,
125 CancellationToken cancellationToken)
126 {
127 CheckVersionParameter(version);
128
129 using (await SemaphoreSlimContext.Lock(changeDeleteSemaphore, cancellationToken))
130 {
131 using var installLock = await AssertAndLockVersion(
132 progressReporter,
133 version,
134 customVersionStream,
135 false,
136 allowInstallation,
137 cancellationToken);
138
139 // We reparse the version because it could be changed after a custom install.
140 version = new EngineVersion(installLock.Version);
141
142 var stringVersion = version.ToString();
143 await ioManager.WriteAllBytes(ActiveVersionFileName, Encoding.UTF8.GetBytes(stringVersion), cancellationToken);
145 EventType.EngineActiveVersionChange,
146 new List<string?>
147 {
148 ActiveVersion?.ToString(),
149 stringVersion,
150 },
151 false,
152 cancellationToken);
153
154 ActiveVersion = version;
155
156 logger.LogInformation("Active version changed to {version}", version);
157 var oldTcs = Interlocked.Exchange(ref activeVersionChanged, new TaskCompletionSource());
158 oldTcs.SetResult();
159 }
160 }
161
163 public async ValueTask<IEngineExecutableLock> UseExecutables(EngineVersion? requiredVersion, string? trustDmbFullPath, CancellationToken cancellationToken)
164 {
165 logger.LogTrace(
166 "Acquiring lock on BYOND version {version}...",
167 requiredVersion?.ToString() ?? $"{ActiveVersion} (active)");
168 var versionToUse = requiredVersion ?? ActiveVersion ?? throw new JobException(ErrorCode.EngineNoVersionsInstalled);
169
170 using var progressReporter = new JobProgressReporter();
171
172 var installLock = await AssertAndLockVersion(
173 progressReporter,
174 versionToUse,
175 null,
176 requiredVersion != null,
177 true,
178 cancellationToken);
179 try
180 {
181 if (trustDmbFullPath != null)
182 await engineInstaller.TrustDmbPath(installLock.Version, trustDmbFullPath, cancellationToken);
183
184 return installLock;
185 }
186 catch
187 {
188 installLock.Dispose();
189 throw;
190 }
191 }
192
194 public async ValueTask DeleteVersion(JobProgressReporter progressReporter, EngineVersion version, CancellationToken cancellationToken)
195 {
196 ArgumentNullException.ThrowIfNull(progressReporter);
197
198 CheckVersionParameter(version);
199
200 logger.LogTrace("DeleteVersion {version}", version);
201
202 var activeVersion = ActiveVersion;
203 if (activeVersion != null && version.Equals(activeVersion))
204 throw new JobException(ErrorCode.EngineCannotDeleteActiveVersion);
205
207 logger.LogTrace("Waiting to acquire installedVersions lock...");
208 lock (installedVersions)
209 {
210 if (!installedVersions.TryGetValue(version, out var containerNullable))
211 {
212 logger.LogTrace("Version {version} already deleted.", version);
213 return;
214 }
215
216 container = containerNullable;
217 logger.LogTrace("Installation container acquired for deletion");
218 }
219
220 progressReporter.StageName = "Waiting for version to not be in use...";
221 while (true)
222 {
223 var containerTask = container.OnZeroReferences;
224
225 // We also want to check when the active version changes in case we need to fail the job because of that.
226 Task activeVersionUpdate;
227 using (await SemaphoreSlimContext.Lock(changeDeleteSemaphore, cancellationToken))
228 activeVersionUpdate = activeVersionChanged.Task;
229
230 logger.LogTrace("Waiting for container.OnZeroReferences or switch of active version...");
231 await Task.WhenAny(
232 containerTask,
233 activeVersionUpdate)
234 .WaitAsync(cancellationToken);
235
236 if (containerTask.IsCompleted)
237 logger.LogTrace("All locks for version {version} are gone", version);
238 else
239 logger.LogTrace("activeVersion changed, we may have to wait again. Acquiring semaphore...");
240
241 using (await SemaphoreSlimContext.Lock(changeDeleteSemaphore, cancellationToken))
242 {
243 // check again because it could have become the active version.
244 activeVersion = ActiveVersion;
245 if (activeVersion != null && version.Equals(activeVersion))
246 throw new JobException(ErrorCode.EngineCannotDeleteActiveVersion);
247
248 bool proceed;
249 logger.LogTrace("Locking installedVersions...");
250 lock (installedVersions)
251 {
252 proceed = container.OnZeroReferences.IsCompleted;
253 if (proceed)
254 if (!installedVersions.TryGetValue(version, out var newerContainer))
255 logger.LogWarning("Unable to remove engine installation {version} from list! Is there a duplicate job running?", version);
256 else
257 {
258 if (container != newerContainer)
259 {
260 // Okay let me get this straight, there was a duplicate delete job, it ran before us after we grabbed the container, AND another installation of the same version completed?
261 // I know realistically this is practically impossible, but god damn that small possiblility
262 // best thing to do is check we exclusively own the newer container
263 logger.LogDebug("Extreme race condition encountered, applying concentrated copium...");
264 container = newerContainer;
265 proceed = container.OnZeroReferences.IsCompleted;
266 }
267
268 if (proceed)
269 {
270 logger.LogTrace("Proceeding with installation deletion...");
271 installedVersions.Remove(version);
272 }
273 }
274 }
275
276 if (proceed)
277 {
278 logger.LogInformation("Deleting version {version}...", version);
279 progressReporter.StageName = "Deleting installation...";
280
281 // delete the version file first, because we will know not to re-discover the installation if it's not present and it will get cleaned on reboot
282 var installPath = version.ToString();
283 await ioManager.DeleteFile(
285 cancellationToken);
286 await ioManager.DeleteDirectory(installPath, cancellationToken);
287 return;
288 }
289
290 if (containerTask.IsCompleted)
291 logger.LogDebug(
292 "Another lock was acquired before we could remove version {version} from the list. We will have to wait again.",
293 version);
294 else
295 logger.LogTrace("Not proceeding for some reason or another");
296 }
297 }
298 }
299
301 public async Task StartAsync(CancellationToken cancellationToken)
302 {
303 async ValueTask<byte[]?> GetActiveVersion()
304 {
305 var activeVersionFileExists = await ioManager.FileExists(ActiveVersionFileName, cancellationToken);
306 return !activeVersionFileExists ? null : await ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken);
307 }
308
309 var activeVersionBytesTask = GetActiveVersion();
310
312 var directories = await ioManager.GetDirectories(DefaultIOManager.CurrentDirectory, cancellationToken);
313
314 var installedVersionPaths = new Dictionary<string, EngineVersion>();
315
316 async ValueTask ReadVersion(string path)
317 {
318 var versionFile = ioManager.ConcatPath(path, VersionFileName);
319 if (!await ioManager.FileExists(versionFile, cancellationToken))
320 {
321 logger.LogWarning("Cleaning path with no version file: {versionPath}", ioManager.ResolvePath(path));
322 await ioManager.DeleteDirectory(path, cancellationToken); // cleanup
323 return;
324 }
325
326 var bytes = await ioManager.ReadAllBytes(versionFile, cancellationToken);
327 var text = Encoding.UTF8.GetString(bytes);
328 EngineVersion version;
329 if (!EngineVersion.TryParse(text, out var versionNullable))
330 {
331 logger.LogWarning("Cleaning path with unparsable version file: {versionPath}", ioManager.ResolvePath(path));
332 await ioManager.DeleteDirectory(path, cancellationToken); // cleanup
333 return;
334 }
335 else
336 version = versionNullable!;
337
338 try
339 {
340 AddInstallationContainer(version, path, Task.CompletedTask);
341 logger.LogDebug("Added detected BYOND version {versionKey}...", version);
342 }
343 catch (Exception ex)
344 {
345 logger.LogWarning(
346 ex,
347 "It seems that there are multiple directories that say they contain BYOND version {version}. We're ignoring and cleaning the duplicate: {duplicatePath}",
348 version,
349 ioManager.ResolvePath(path));
350 await ioManager.DeleteDirectory(path, cancellationToken);
351 return;
352 }
353
354 lock (installedVersionPaths)
355 installedVersionPaths.Add(ioManager.ResolvePath(version.ToString()), version);
356 }
357
359 directories
360 .Select(ReadVersion));
361
362 logger.LogTrace("Upgrading BYOND installations...");
364 installedVersionPaths
365 .Select(kvp => engineInstaller.UpgradeInstallation(kvp.Value, kvp.Key, cancellationToken)));
366
367 var activeVersionBytes = await activeVersionBytesTask;
368 if (activeVersionBytes != null)
369 {
370 var activeVersionString = Encoding.UTF8.GetString(activeVersionBytes);
371
372 EngineVersion? activeVersion;
373 bool hasRequestedActiveVersion;
374 lock (installedVersions)
375 hasRequestedActiveVersion = EngineVersion.TryParse(activeVersionString, out activeVersion)
376 && installedVersions.ContainsKey(activeVersion!);
377
378 if (hasRequestedActiveVersion)
379 ActiveVersion = activeVersion; // not setting TCS because there's no need during init
380 else
381 {
382 logger.LogWarning("Failed to load saved active version {activeVersion}!", activeVersionString);
383 await ioManager.DeleteFile(ActiveVersionFileName, cancellationToken);
384 }
385 }
386 }
387
389 public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
390
401 async ValueTask<EngineExecutableLock> AssertAndLockVersion(
402 JobProgressReporter progressReporter,
403 EngineVersion version,
404 Stream? customVersionStream,
405 bool neededForLock,
406 bool allowInstallation,
407 CancellationToken cancellationToken)
408 {
409 var ourTcs = new TaskCompletionSource();
410 IEngineInstallation installation;
411 EngineExecutableLock installLock;
412 bool installedOrInstalling;
413 lock (installedVersions)
414 {
415 if (customVersionStream != null)
416 {
417 var customInstallationNumber = 1;
418 do
419 {
420 version.CustomIteration = customInstallationNumber++;
421 }
422 while (installedVersions.ContainsKey(version));
423 }
424
425 installedOrInstalling = installedVersions.TryGetValue(version, out var installationContainerNullable);
427 if (!installedOrInstalling)
428 {
429 if (!allowInstallation)
430 throw new InvalidOperationException($"Engine version {version} not installed!");
431
432 installationContainer = AddInstallationContainer(
433 version,
434 ioManager.ResolvePath(version.ToString()),
435 ourTcs.Task);
436 }
437 else
438 installationContainer = installationContainerNullable!;
439
440 installation = installationContainer.Instance;
441 installLock = installationContainer.AddReference();
442 }
443
444 var deploymentPipelineProcesses = !neededForLock;
445 try
446 {
447 if (installedOrInstalling)
448 {
449 progressReporter.StageName = "Waiting for existing installation job...";
450
451 if (neededForLock && !installation.InstallationTask.IsCompleted)
452 logger.LogWarning("The required engine version ({version}) is not readily available! We will have to wait for it to install.", version);
453
454 await installation.InstallationTask.WaitAsync(cancellationToken);
455 return installLock;
456 }
457
458 // okay up to us to install it then
459 string? installPath = null;
460 try
461 {
462 if (customVersionStream != null)
463 logger.LogInformation("Installing custom engine version as {version}...", version);
464 else if (neededForLock)
465 {
466 if (version.CustomIteration.HasValue)
467 throw new JobException(ErrorCode.EngineNonExistentCustomVersion);
468
469 logger.LogWarning("The required engine version ({version}) is not readily available! We will have to install it.", version);
470 }
471 else
472 logger.LogInformation("Requested engine version {version} not currently installed. Doing so now...", version);
473
474 progressReporter.StageName = "Running event";
475
476 var versionString = version.ToString();
477 await eventConsumer.HandleEvent(EventType.EngineInstallStart, new List<string> { versionString }, deploymentPipelineProcesses, cancellationToken);
478
479 installPath = await InstallVersionFiles(progressReporter, version, customVersionStream, deploymentPipelineProcesses, cancellationToken);
480 await eventConsumer.HandleEvent(EventType.EngineInstallComplete, new List<string> { versionString }, deploymentPipelineProcesses, cancellationToken);
481
482 ourTcs.SetResult();
483 }
484 catch (Exception ex)
485 {
486 if (installPath != null)
487 {
488 try
489 {
490 logger.LogDebug("Cleaning up failed installation at {path}...", installPath);
491 await ioManager.DeleteDirectory(installPath, cancellationToken);
492 }
493 catch (Exception ex2)
494 {
495 logger.LogError(ex2, "Error cleaning up failed installation!");
496 }
497 }
498 else if (ex is not OperationCanceledException)
499 await eventConsumer.HandleEvent(EventType.EngineInstallFail, new List<string> { ex.Message }, deploymentPipelineProcesses, cancellationToken);
500
501 lock (installedVersions)
502 installedVersions.Remove(version);
503
504 ourTcs.SetException(ex);
505 throw;
506 }
507
508 return installLock;
509 }
510 catch
511 {
512 installLock.Dispose();
513 throw;
514 }
515 }
516
526 async ValueTask<string> InstallVersionFiles(
527 JobProgressReporter progressReporter,
528 EngineVersion version,
529 Stream? customVersionStream,
530 bool deploymentPipelineProcesses,
531 CancellationToken cancellationToken)
532 {
533 var installFullPath = ioManager.ResolvePath(version.ToString());
534 async ValueTask DirectoryCleanup()
535 {
536 await ioManager.DeleteDirectory(installFullPath, cancellationToken);
537 await ioManager.CreateDirectory(installFullPath, cancellationToken);
538 }
539
540 var directoryCleanupTask = DirectoryCleanup();
541 try
542 {
543 IEngineInstallationData engineInstallationData;
544 var remainingProgress = 1.0;
545 if (customVersionStream == null)
546 {
547 using var subReporter = progressReporter.CreateSection("Downloading Version", 0.5);
548 remainingProgress -= 0.5;
549 engineInstallationData = await engineInstaller.DownloadVersion(version, subReporter, cancellationToken);
550 }
551 else
552#pragma warning disable CA2000 // Dispose objects before losing scope, false positive
553 engineInstallationData = new ZipStreamEngineInstallationData(
554 ioManager,
555 customVersionStream);
556#pragma warning restore CA2000 // Dispose objects before losing scope
557
558 JobProgressReporter remainingReporter;
559 try
560 {
561 remainingReporter = progressReporter.CreateSection(null, remainingProgress);
562 }
563 catch
564 {
565 await engineInstallationData.DisposeAsync();
566 throw;
567 }
568
569 using (remainingReporter)
570 {
571 await using (engineInstallationData)
572 {
573 remainingReporter.StageName = "Cleaning target directory";
574
575 await directoryCleanupTask;
576 remainingReporter.ReportProgress(0.1);
577 remainingReporter.StageName = "Extracting data";
578
579 logger.LogTrace("Extracting engine to {extractPath}...", installFullPath);
580 await engineInstallationData.ExtractToPath(installFullPath, cancellationToken);
581 remainingReporter.ReportProgress(0.3);
582 }
583
584 remainingReporter.StageName = "Running installation actions";
585
586 await engineInstaller.Install(version, installFullPath, deploymentPipelineProcesses, cancellationToken);
587
588 remainingReporter.ReportProgress(0.9);
589 remainingReporter.StageName = "Writing version file";
590
591 // make sure to do this last because this is what tells us we have a valid version in the future
593 ioManager.ConcatPath(installFullPath, VersionFileName),
594 Encoding.UTF8.GetBytes(version.ToString()),
595 cancellationToken);
596 }
597 }
598 catch (HttpRequestException ex)
599 {
600 // since the user can easily provide non-exitent version numbers, we'll turn this into a JobException
601 throw new JobException(ErrorCode.EngineDownloadFail, ex);
602 }
603 catch (OperationCanceledException)
604 {
605 throw;
606 }
607 catch
608 {
609 await ioManager.DeleteDirectory(installFullPath, cancellationToken);
610 throw;
611 }
612
613 return installFullPath;
614 }
615
624 {
625 var installation = engineInstaller.CreateInstallation(version, installPath, installationTask);
626
627 var installationContainer = new ReferenceCountingContainer<IEngineInstallation, EngineExecutableLock>(installation);
628
629 lock (installedVersions)
630 installedVersions.Add(version, installationContainer);
631
632 return installationContainer;
633 }
634 }
635}
Information about an engine installation.
static bool TryParse(string input, out EngineVersion? engineVersion)
Attempts to parse a stringified EngineVersion.
int? CustomIteration
The revision of the custom build.
Extension methods for the ValueTask and ValueTask<TResult> classes.
static async ValueTask WhenAll(IEnumerable< ValueTask > tasks)
Fully await a given list of tasks .
static void CheckVersionParameter(EngineVersion version)
Validates a given version parameter.
readonly Dictionary< EngineVersion, ReferenceCountingContainer< IEngineInstallation, EngineExecutableLock > > installedVersions
Map of byond EngineVersions to Tasks that complete when they are installed.
Task StopAsync(CancellationToken cancellationToken)
const string VersionFileName
The file in which we store the Version for installations.
async Task StartAsync(CancellationToken cancellationToken)
IReadOnlyList< EngineVersion > InstalledVersions
The installed EngineVersions.
async ValueTask< EngineExecutableLock > AssertAndLockVersion(JobProgressReporter progressReporter, EngineVersion version, Stream? customVersionStream, bool neededForLock, bool allowInstallation, CancellationToken cancellationToken)
Ensures a BYOND version is installed if it isn't already.
volatile TaskCompletionSource activeVersionChanged
TaskCompletionSource that notifes when the ActiveVersion changes.
const string ActiveVersionFileName
The file in which we store the ActiveVersion.
async ValueTask< IEngineExecutableLock > UseExecutables(EngineVersion? requiredVersion, string? trustDmbFullPath, CancellationToken cancellationToken)
Lock the current installation's location and return a IEngineExecutableLock.A ValueTask<TResult> resu...
async ValueTask DeleteVersion(JobProgressReporter progressReporter, EngineVersion version, CancellationToken cancellationToken)
Deletes a given version from the disk.A Task representing the running operation.
EngineVersion? ActiveVersion
The currently active EngineVersion.
EngineManager(IIOManager ioManager, IEngineInstaller engineInstaller, IEventConsumer eventConsumer, ILogger< EngineManager > logger)
Initializes a new instance of the EngineManager class.
readonly SemaphoreSlim changeDeleteSemaphore
The SemaphoreSlim for changing or deleting the active BYOND version.
async ValueTask ChangeVersion(JobProgressReporter progressReporter, EngineVersion version, Stream? customVersionStream, bool allowInstallation, CancellationToken cancellationToken)
Change the active EngineVersion.A ValueTask representing the running operation.
readonly IEventConsumer eventConsumer
The IEventConsumer for the EngineManager.
readonly IIOManager ioManager
The IIOManager for the EngineManager.
async ValueTask< string > InstallVersionFiles(JobProgressReporter progressReporter, EngineVersion version, Stream? customVersionStream, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
Installs the files for a given BYOND version .
readonly IEngineInstaller engineInstaller
The IEngineInstaller for the EngineManager.
readonly ILogger< EngineManager > logger
The ILogger for the EngineManager.
ReferenceCountingContainer< IEngineInstallation, EngineExecutableLock > AddInstallationContainer(EngineVersion version, string installPath, Task installationTask)
Create and add a new IEngineInstallation to installedVersions.
Implementation of IEngineInstallationData for a zip file in a Stream.
IIOManager that resolves paths to Environment.CurrentDirectory.
const string CurrentDirectory
Path to the current working directory for the IIOManager.
Operation exceptions thrown from the context of a Models.Job.
JobProgressReporter CreateSection(string? newStageName, double percentage)
Create a subsection of the JobProgressReporter with its optional own stage name.
void ReportProgress(double? progress)
Report progress.
TReference AddReference()
Create a new TReference to the Instance.
Task OnZeroReferences
A Task that completes when there are no TReference s active for the Instance.
static async ValueTask< SemaphoreSlimContext > Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken, ILogger? logger=null)
Asyncronously locks a semaphore .
Task ExtractToPath(string path, CancellationToken cancellationToken)
Extracts the installation to a given path.
Task InstallationTask
The Task that completes when the BYOND version finished installing.
For downloading and installing game engines for a given system.
IEngineInstallation CreateInstallation(EngineVersion version, string path, Task installationTask)
Creates an IEngineInstallation for a given version .
ValueTask< IEngineInstallationData > DownloadVersion(EngineVersion version, JobProgressReporter jobProgressReporter, CancellationToken cancellationToken)
Download a given engine version .
ValueTask UpgradeInstallation(EngineVersion version, string path, CancellationToken cancellationToken)
Does actions necessary to get upgrade a version installed by a previous version of TGS.
ValueTask Install(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
Does actions necessary to get an extracted installation working.
ValueTask TrustDmbPath(EngineVersion version, string fullDmbPath, CancellationToken cancellationToken)
Add a given fullDmbPath to the trusted DMBs list in BYOND's config.
Consumes EventTypes and takes the appropriate actions.
ValueTask HandleEvent(EventType eventType, IEnumerable< string?> parameters, bool deploymentPipeline, CancellationToken cancellationToken)
Handle a given eventType .
Interface for using filesystems.
Definition IIOManager.cs:13
string ResolvePath()
Retrieve the full path of the current working directory.
ValueTask< byte[]> ReadAllBytes(string path, CancellationToken cancellationToken)
Returns all the contents of a file at path as a byte array.
string ConcatPath(params string[] paths)
Combines an array of strings into a path.
Task< IReadOnlyList< string > > GetDirectories(string path, CancellationToken cancellationToken)
Returns full directory names in a given path .
Task CreateDirectory(string path, CancellationToken cancellationToken)
Create a directory at path .
Task DeleteFile(string path, CancellationToken cancellationToken)
Deletes a file at path .
ValueTask WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
Writes some contents to a file at path overwriting previous content.
Task DeleteDirectory(string path, CancellationToken cancellationToken)
Recursively delete a directory, removes and does not enter any symlinks encounterd.
Task< bool > FileExists(string path, CancellationToken cancellationToken)
Check that the file at path exists.
ErrorCode
Types of Response.ErrorMessageResponses that the API may return.
Definition ErrorCode.cs:12
EventType
Types of events. Mirror in tgs.dm. Prefer last listed name for script.
Definition EventType.cs:7