2using System.Collections.Generic;
3using System.Globalization;
7using System.Threading.Tasks;
9using Microsoft.Extensions.Logging;
37 internal static bool LogTopicRequests {
get;
set; } =
true;
48 throw new InvalidOperationException(
"ApiValidated cannot be checked while Lifetime is incomplete!");
86 var tcs =
new TaskCompletionSource<Task>();
89 var toAwait = await tcs.Task;
94 tcs.SetResult(Interlocked.Exchange(ref
rebootGate, Wrap()));
251 ILogger<SessionController> logger,
252 Func<ValueTask> postLifetimeCallback,
253 uint? startupTimeout,
258 ReattachInformation = reattachInformation ??
throw new ArgumentNullException(nameof(reattachInformation));
259 this.metadata =
metadata ??
throw new ArgumentNullException(nameof(
metadata));
260 this.process =
process ??
throw new ArgumentNullException(nameof(
process));
264 ArgumentNullException.ThrowIfNull(bridgeRegistrar);
266 this.chat =
chat ??
throw new ArgumentNullException(nameof(
chat));
267 ArgumentNullException.ThrowIfNull(assemblyInformationProvider);
279 primeTcs =
new TaskCompletionSource();
299 "Not registering session with {reasonWhyDmApiIsBad} DMAPI version for interop!",
302 : $
"incompatible ({reattachInformation.Dmb.CompileJob.DMApiVersion})");
304 async Task<int?> WrapLifetime()
307 await postLifetimeCallback();
317 assemblyInformationProvider,
324 "Created session controller. CommsKey: {accessIdentifier}, Port: {port}",
326 reattachInformation.
Port);
339 Logger.LogTrace(
"Disposing...");
342 var cancellationToken = CancellationToken.None;
360 if (initialDmb !=
null)
361 await initialDmb.DisposeAsync();
363 await regularDmbDisposeTask;
371 (await semaphoreLockTask).Dispose();
378 ArgumentNullException.ThrowIfNull(parameters);
382 Logger.LogTrace(
"Handling bridge request...");
409 =>
SendCommand(parameters,
false, cancellationToken);
417 Logger.LogTrace(
"Changing reboot state to {newRebootState}", newRebootState);
419 ReattachInformation.RebootState = newRebootState;
424 return result !=
null && result.ErrorMessage ==
null;
431 Logger.LogTrace(
"Resetting reboot state...");
432 ReattachInformation.RebootState =
RebootState.Normal;
448 ReattachInformation.Dmb = dmbProvider ??
throw new ArgumentNullException(nameof(dmbProvider));
453 public async ValueTask
InstanceRenamed(
string newInstanceName, CancellationToken cancellationToken)
456 if (runtimeInformation !=
null)
457 runtimeInformation.InstanceName = newInstanceName;
465 public async ValueTask
UpdateChannels(IEnumerable<ChannelRepresentation> newChannels, CancellationToken cancellationToken)
486 uint? startupTimeout,
490 var startTime = DateTimeOffset.UtcNow;
491 var useBridgeRequestForLaunchResult = !reattached && (apiValidate ||
DMApiAvailable);
492 var startupTask = useBridgeRequestForLaunchResult
493 ? initialBridgeRequestTcs.Task
497 if (startupTimeout.HasValue)
498 toAwait = Task.WhenAny(
501 TimeSpan.FromSeconds(startupTimeout.Value),
502 CancellationToken.None));
505 "Waiting for LaunchResult based on {launchResultCompletionCause}{possibleTimeout}...",
506 useBridgeRequestForLaunchResult ?
"initial bridge request" :
"process startup",
507 startupTimeout.HasValue ? $
" with a timeout of {startupTimeout.Value}s" : String.Empty);
513 ExitCode = process.Lifetime.IsCompleted ? await process.Lifetime :
null,
514 StartupTime = startupTask.IsCompleted ? (DateTimeOffset.UtcNow - startTime) :
null,
517 Logger.LogTrace(
"Launch result: {launchResult}", result);
519 if (!result.ExitCode.HasValue && reattached && !
disposed)
523 assemblyInformationProvider.
Version,
528 if (reattachResponse !=
null)
530 if (reattachResponse?.CustomCommands !=
null)
531 chatTrackingContext.CustomCommands = reattachResponse.CustomCommands;
532 else if (reattachResponse !=
null)
537 "DMAPI Interop v{interopVersion} isn't returning the TGS custom commands list. Functionality added in v5.2.0.",
557 Logger.LogTrace(
"Entered post validation terminate task.");
558 if (!await proceedTask)
560 Logger.LogTrace(
"Not running post validation terminate task for repeated bridge request.");
564 const int GracePeriodSeconds = 30;
565 Logger.LogDebug(
"Server will terminated in {gracePeriodSeconds}s if it does not exit...", GracePeriodSeconds);
566 var delayTask =
asyncDelayer.
Delay(TimeSpan.FromSeconds(GracePeriodSeconds), CancellationToken.None);
571 Logger.LogWarning(
"DMAPI took too long to shutdown server after validation request!");
576 Logger.LogTrace(
"Server exited properly post validation.");
585#pragma warning disable CA1502
596 return BridgeError(
"Missing channelIds field in chatMessage!");
598 if (parameters.
ChatMessage.
ChannelIds.Any(channelIdString => !UInt64.TryParse(channelIdString, out var _)))
599 return BridgeError(
"Invalid channelIds in chatMessage!");
602 return BridgeError(
"Missing message field in chatMessage!");
604 var anyFailed =
false;
608 anyFailed |= !UInt64.TryParse(channelString, out var channelId);
613 return BridgeError(
"Failed to parse channelIds as U64!");
620 Interlocked.Exchange(ref
primeTcs,
new TaskCompletionSource()).SetResult();
623 Logger.LogInformation(
"Bridge requested process termination!");
624 chatTrackingContext.Active =
false;
629 return BridgeError(
"Port switching is no longer supported!");
635 var proceedTcs =
new TaskCompletionSource<bool>();
637 proceedTcs.SetResult(firstValidationRequest);
639 if (!firstValidationRequest)
640 return BridgeError(
"Startup bridge request was repeated!");
643 if (parameters.
Version ==
null)
668 return BridgeError(
"Missing minimumSecurityLevel field!");
670 return BridgeError(
"Invalid minimumSecurityLevel!");
688 var newTopicPort = parameters.
TopicPort.Value;
689 Logger.LogInformation(
"Server is requesting use of port {topicPort} for topic communications", newTopicPort);
690 ReattachInformation.TopicPort = newTopicPort;
694 chatTrackingContext.CustomCommands = parameters.CustomCommands ?? Array.Empty<
CustomCommand>();
695 chatTrackingContext.Active =
true;
696 Interlocked.Exchange(ref
startupTcs,
new TaskCompletionSource()).SetResult();
702 chatTrackingContext.Active =
false;
703 Interlocked.Exchange(ref
rebootTcs,
new TaskCompletionSource()).SetResult();
704 await
RebootGate.WaitAsync(cancellationToken);
717 return BridgeError($
"commandType {parameters.CommandType} not supported!");
722#pragma warning restore CA1502
731 Logger.LogWarning(
"Bridge request error: {message}", message);
734 ErrorMessage = message,
749 if (LogTopicRequests)
750 Logger.LogTrace(
"Topic request: {json}", json);
751 var fullCommandByteCount = Encoding.UTF8.GetByteCount(fullCommandString);
754 return await
SendRawTopic(fullCommandString, topicPriority, cancellationToken);
756 var interopChunkingVersion =
new Version(5, 6, 0);
760 "Cannot send topic request as it is exceeds the single request limit of {limitBytes}B ({actualBytes}B) and requires chunking and the current compile job's interop version must be at least {chunkingVersionRequired}!",
762 fullCommandByteCount,
763 interopChunkingVersion);
770 parameters.AccessIdentifier =
null!;
775 var fullPayloadSize = (uint)json.Length;
777 List<string>? chunkQueryStrings =
null;
778 for (var chunkCount = 2; chunkQueryStrings ==
null; ++chunkCount)
780 var standardChunkSize = fullPayloadSize / chunkCount;
781 var bigChunkSize = standardChunkSize + (fullPayloadSize % chunkCount);
785 chunkQueryStrings =
new List<string>();
786 for (var i = 0U; i < chunkCount; ++i)
788 var startIndex = i * standardChunkSize;
789 var subStringLength = Math.Min(
790 fullPayloadSize - startIndex,
793 : standardChunkSize);
794 var chunkPayload = json.Substring((
int)startIndex, (
int)subStringLength);
798 Payload = chunkPayload,
799 PayloadId = payloadId,
801 TotalChunks = (uint)chunkCount,
813 chunkQueryStrings =
null;
817 chunkQueryStrings.Add(chunkCommandString);
821 Logger.LogTrace(
"Chunking topic request ({totalChunks} total)...", chunkQueryStrings.Count);
824 bool LogRequestIssue(
bool possiblyFromCompletedRequest)
829 "Topic request {chunkingStatus} failed!{potentialRequestError}",
830 possiblyFromCompletedRequest ?
"final chunk" :
"chunking",
831 combinedResponse?.InteropResponse?.ErrorMessage !=
null
832 ? $
" Request error: {combinedResponse.InteropResponse.ErrorMessage}"
840 foreach (var chunkCommandString
in chunkQueryStrings)
842 combinedResponse = await
SendRawTopic(chunkCommandString, topicPriority, cancellationToken);
843 if (LogRequestIssue(chunkCommandString == chunkQueryStrings.Last()))
847 while ((combinedResponse?.InteropResponse?.MissingChunks?.Count ?? 0) > 0)
849 Logger.LogWarning(
"DD is still missing some chunks of topic request P{payloadId}! Sending missing chunks...", payloadId);
850 var missingChunks = combinedResponse!.InteropResponse!.MissingChunks!;
851 var lastIndex = missingChunks.Last();
852 foreach (var missingChunkIndex
in missingChunks)
854 var chunkCommandString = chunkQueryStrings[(int)missingChunkIndex];
855 combinedResponse = await
SendRawTopic(chunkCommandString, topicPriority, cancellationToken);
856 if (LogRequestIssue(missingChunkIndex == lastIndex))
861 return combinedResponse;
873 var commandString = String.Format(
874 CultureInfo.InvariantCulture,
878 return commandString;
888 async ValueTask<CombinedTopicResponse?>
SendRawTopic(
string queryString,
bool priority, CancellationToken cancellationToken)
893 "Attempted to send a topic on a disposed SessionController");
898 Byond.TopicSender.TopicResponse? byondResponse;
908 if (byondResponse ==
null)
912 "Unable to send priority topic \"{queryString}\"!",
918 var topicReturn = byondResponse.StringData;
921 if (topicReturn !=
null)
928 Logger.LogWarning(ex,
"Invalid interop response: {topicReturnString}", topicReturn);
943 ArgumentNullException.ThrowIfNull(parameters);
948 "Attempted to send a command to an inactive SessionController: {commandType}",
955 Logger.LogTrace(
"Not sending topic request {commandType} to server without/with incompatible DMAPI!", parameters.
CommandType);
960 if (!bypassLaunchResult)
962 var launchResult = await
LaunchResult.WaitAsync(cancellationToken);
963 if (launchResult.ExitCode.HasValue)
965 Logger.LogDebug(
"Not sending topic request {commandType} to server that failed to launch!", parameters.
CommandType);
973 Logger.LogDebug(
"Not sending topic request {commandType} to server that is rebooting/starting.", parameters.
CommandType);
977 using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
978 var combinedCancellationToken = cts.Token;
979 async ValueTask CancelIfLifetimeElapses()
983 var completed = await Task.WhenAny(
Lifetime, reboot).WaitAsync(combinedCancellationToken);
986 "Server {action}, cancelling pending command: {commandType}",
993 catch (OperationCanceledException)
999 Logger.LogError(ex,
"Error in CancelIfLifetimeElapses!");
1004 var lifetimeWatchingTask = CancelIfLifetimeElapses();
1007 var combinedResponse = await
SendTopicRequest(parameters, combinedCancellationToken);
1009 void LogCombinedResponse()
1011 if (LogTopicRequests && combinedResponse !=
null)
1012 Logger.LogTrace(
"Topic response: {topicString}", combinedResponse.ByondTopicResponse.StringData ??
"(NO STRING DATA)");
1015 LogCombinedResponse();
1017 if (combinedResponse?.InteropResponse?.Chunk !=
null)
1019 Logger.LogTrace(
"Topic response is chunked...");
1021 ChunkData? nextChunk = combinedResponse.InteropResponse.Chunk;
1024 var nextRequest = await ProcessChunk<TopicResponse, ChunkedTopicParameters>(
1025 (completedResponse, _) =>
1027 fullResponse = completedResponse;
1032 Logger.LogWarning(
"Topic response chunking error: {message}", error);
1035 combinedResponse?.InteropResponse?.Chunk,
1036 combinedCancellationToken);
1038 if (nextRequest !=
null)
1040 nextRequest.PayloadId = nextChunk.
PayloadId;
1041 combinedResponse = await
SendTopicRequest(nextRequest, combinedCancellationToken);
1042 LogCombinedResponse();
1043 nextChunk = combinedResponse?.InteropResponse?.Chunk;
1048 while (nextChunk !=
null);
1051 fullResponse = combinedResponse?.InteropResponse;
1053 catch (OperationCanceledException ex)
1057 "Topic request {cancellationType}!",
1058 combinedCancellationToken.IsCancellationRequested
1059 ? cancellationToken.IsCancellationRequested
1065 cancellationToken.ThrowIfCancellationRequested();
1070 await lifetimeWatchingTask;
1073 if (fullResponse?.ErrorMessage !=
null)
1075 "Errored topic response for command {commandType}: {errorMessage}",
1079 return fullResponse;
Information about an engine installation.
EngineType? Engine
The EngineType.
Metadata about a server instance.
Represents a deployment run.
virtual ? Version DMApiVersion
The DMAPI Version.
Represents a command made from DM code.
Parameters for a bridge request.
BridgeCommandType? CommandType
The BridgeCommandType.
ChatMessage? ChatMessage
The Interop.ChatMessage for BridgeCommandType.ChatSend requests.
ushort? TopicPort
The port that should be used to send world topics, if not the default.
DreamDaemonSecurity? MinimumSecurityLevel
The minimum required DreamDaemonSecurity level for BridgeCommandType.Startup requests.
ChunkData? Chunk
The ChunkData for BridgeCommandType.Chunk requests.
Version? Version
The DMAPI global::System.Version for BridgeCommandType.Startup requests.
A response to a bridge request.
ICollection< string >? ChannelIds
The ICollection<T> of Chat.ChannelRepresentation.Ids to sent the MessageContent to....
Represents an update of ChannelRepresentations.
A packet of a split serialized set of data.
uint? PayloadId
The ID of the full request to differentiate different chunkings. Nullable to prevent default value om...
Class that deserializes chunked interop payloads.
ILogger< Chunker > Logger
The ILogger for the Chunker.
uint NextPayloadId
Gets a payload ID for use in a new ChunkSetInfo.
Constants used for communication with the DMAPI.
static readonly JsonSerializerSettings SerializerSettings
JsonSerializerSettings for use when communicating with the DMAPI.
const uint MaximumTopicRequestLength
The maximum length in bytes of a Byond.TopicSender.ITopicClient payload.
static readonly Version InteropVersion
The DMAPI InteropVersion being used.
const string TopicData
Parameter json is encoded in for topic requests.
Common base for interop parameters.
string AccessIdentifier
Used to identify and authenticate the DreamDaemon instance.
string? ErrorMessage
Any errors in the client's parameters.
string? Text
The message string.
TopicParameters for TopicCommandType.ReceiveChunk.
Parameters for a topic request.
static TopicParameters CreateInstanceRenamedTopicParameters(string newInstanceName)
Initializes a new instance of the TopicParameters class.
TopicCommandType? CommandType
The TopicCommandType.
bool IsPriority
Whether or not the TopicParameters constitute a priority request.
A response to a topic request.
Combines a Byond.TopicSender.TopicResponse with a TopicResponse.
TopicResponse? InteropResponse
The interop TopicResponse, if any.
Represents the result of trying to start a DD process.
async ValueTask InstanceRenamed(string newInstanceName, CancellationToken cancellationToken)
Called when the owning Instance is renamed. A ValueTask representing the running operation.
ValueTask< TopicResponse?> SendCommand(TopicParameters parameters, CancellationToken cancellationToken)
Sends a command to DreamDaemon through /world/Topic(). A ValueTask<TResult> resulting in the TopicRes...
async ValueTask< BridgeResponse?> ProcessBridgeCommand(BridgeParameters parameters, CancellationToken cancellationToken)
Handle a set of bridge parameters .
readonly Byond.TopicSender.ITopicClient byondTopicSender
The Byond.TopicSender.ITopicClient for the SessionController.
SessionController(ReattachInformation reattachInformation, Api.Models.Instance metadata, IProcess process, IEngineExecutableLock engineLock, Byond.TopicSender.ITopicClient byondTopicSender, IChatTrackingContext chatTrackingContext, IBridgeRegistrar bridgeRegistrar, IChatManager chat, IAssemblyInformationProvider assemblyInformationProvider, IAsyncDelayer asyncDelayer, ILogger< SessionController > logger, Func< ValueTask > postLifetimeCallback, uint? startupTimeout, bool reattached, bool apiValidate)
Initializes a new instance of the SessionController class.
ApiValidationStatus apiValidationStatus
The ApiValidationStatus for the SessionController.
async ValueTask DisposeAsync()
readonly object synchronizationLock
lock object for port updates and disposed.
void AdjustPriority(bool higher)
Set's the owned global::System.Diagnostics.Process.PriorityClass to a non-normal value.
async ValueTask< TopicResponse?> SendCommand(TopicParameters parameters, bool bypassLaunchResult, CancellationToken cancellationToken)
Sends a command to DreamDaemon through /world/Topic().
volatile uint rebootBridgeRequestsProcessing
The number of currently active calls to ProcessBridgeRequest(BridgeParameters, CancellationToken) fro...
readonly Api.Models.Instance metadata
The Instance metadata.
Task OnReboot
A Task that completes when the server calls /world/TgsReboot().
bool DMApiAvailable
If the DMAPI may be used this session.
readonly TaskCompletionSource initialBridgeRequestTcs
The TaskCompletionSource that completes when DD makes it's first bridge request.
bool TerminationWasRequested
If the DreamDaemon instance sent a.
bool disposed
If the SessionController has been disposed.
void SuspendProcess()
Suspends the process.
void ResetRebootState()
Changes RebootState to RebootState.Normal without telling the DMAPI.
readonly IEngineExecutableLock engineLock
The IEngineExecutableLock for the SessionController.
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the SessionController.
async ValueTask< BridgeResponse?> ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
Handle a set of bridge parameters . A ValueTask<TResult> resulting in the BridgeResponse for the requ...
ReattachInformation ReattachInformation
The up to date Session.ReattachInformation.
readonly CancellationTokenSource reattachTopicCts
A CancellationTokenSource used for the topic send operation made on reattaching.
volatile Task rebootGate
Backing field for RebootGate.
BridgeResponse BridgeError(string message)
Log and return a BridgeResponse for a given message .
bool released
If process should be kept alive instead.
readonly IChatManager chat
The IChatManager for the SessionController.
readonly IChatTrackingContext chatTrackingContext
The IChatTrackingContext for the SessionController.
Task< int?> Lifetime
The Task<TResult> resulting in the exit code of the process or null if the process was detached.
volatile TaskCompletionSource startupTcs
The TaskCompletionSource that completes when DD sends a valid startup bridge request.
async Task PostValidationShutdown(Task< bool > proceedTask)
Terminates the server after ten seconds if it does not exit.
void ResumeProcess()
Resumes the process.
readonly bool apiValidationSession
If this session is meant to validate the presence of the DMAPI.
FifoSemaphore TopicSendSemaphore
The FifoSemaphore used to prevent concurrent calls into /world/Topic().
async ValueTask UpdateChannels(IEnumerable< ChannelRepresentation > newChannels, CancellationToken cancellationToken)
Called when newChannels are set. A ValueTask representing the running operation.
string GenerateQueryString(TopicParameters parameters, out string json)
Generates a Byond.TopicSender.ITopicClient query string for a given set of parameters .
ValueTask CreateDump(string outputFile, CancellationToken cancellationToken)
Create a dump file of the process. A ValueTask representing the running operation.
void CheckDisposed()
Throws an ObjectDisposedException if DisposeAsync has been called.
readonly? IBridgeRegistration bridgeRegistration
The IBridgeRegistration for the SessionController.
async Task< LaunchResult > GetLaunchResult(IAssemblyInformationProvider assemblyInformationProvider, IAsyncDelayer asyncDelayer, uint? startupTimeout, bool reattached, bool apiValidate)
The Task<TResult> for LaunchResult.
IAsyncDisposable ReplaceDmbProvider(IDmbProvider dmbProvider)
Replace the IDmbProvider in use with a given newProvider , disposing the old one. An IAsyncDisposable...
Task OnStartup
A Task that completes when the server calls /world/TgsNew().
bool ProcessingRebootBridgeRequest
If the ISessionController is currently processing a bridge request from TgsReboot().
ValueTask Release()
Releases the IProcess without terminating it. Also calls IDisposable.Dispose. A ValueTask representin...
volatile? Task postValidationShutdownTask
Task for shutting down the server if it is taking too long after validation.
Task OnPrime
A Task that completes when the server calls /world/TgsInitializationComplete().
async ValueTask< CombinedTopicResponse?> SendTopicRequest(TopicParameters parameters, CancellationToken cancellationToken)
Send a topic request for given parameters to DreamDaemon, chunking it if necessary.
volatile TaskCompletionSource rebootTcs
The TaskCompletionSource that completes when DD tells us about a reboot.
async ValueTask< CombinedTopicResponse?> SendRawTopic(string queryString, bool priority, CancellationToken cancellationToken)
Send a given queryString to DreamDaemon's /world/Topic.
volatile TaskCompletionSource primeTcs
The TaskCompletionSource that completes when DD tells us it's primed.
async ValueTask< bool > SetRebootState(RebootState newRebootState, CancellationToken cancellationToken)
Attempts to change the current RebootState to newRebootState . A ValueTask<TResult> resulting in true...
Task RebootGate
A Task that must complete before a TgsReboot() bridge request can complete.
Version? DMApiVersion
The DMAPI Version.
readonly IProcess process
The IProcess for the SessionController.
A first-in first-out async semaphore.
async ValueTask< SemaphoreSlimContext > Lock(CancellationToken cancellationToken)
Locks the FifoSemaphore.
Helpers for manipulating the Serilog.Context.LogContext.
const string InstanceIdContextProperty
The Serilog.Context.LogContext property name for Models.Instance Api.Models.EntityId....
Notifyee of when ChannelRepresentations in a IChatTrackingContext are updated.
For managing connected chat services.
void QueueMessage(MessageContent message, IEnumerable< ulong > channelIds)
Queue a chat message to a given set of channelIds .
Represents a tracking of dynamic chat json files.
bool Active
If the CustomCommands should be used.
void SetChannelSink(IChannelSink channelSink)
Sets the channelSink for the IChatTrackingContext.
Provides absolute paths to the latest compiled .dmbs.
void KeepAlive()
Disposing the IDmbProvider won't cause a cleanup of the working directory.
EngineVersion EngineVersion
The Api.Models.EngineVersion used to build the .dmb.
Models.CompileJob CompileJob
The CompileJob of the .dmb.
Represents usage of the two primary BYOND server executables.
void DoNotDeleteThisSession()
Call if, during a detach, this version should not be deleted.
ValueTask StopServerProcess(ILogger logger, IProcess process, string accessIdentifier, ushort port, CancellationToken cancellationToken)
Kills a given engine server process .
Registers IBridgeHandlers.
IBridgeRegistration RegisterHandler(IBridgeHandler bridgeHandler)
Register a given bridgeHandler .
Represents a registration of an interop session.
Handles communication with a DreamDaemon IProcess.
ValueTask CreateDump(string outputFile, CancellationToken cancellationToken)
Create a dump file of the process.
void SuspendProcess()
Suspends the process.
void ResumeProcess()
Resumes the process.
void AdjustPriority(bool higher)
Set's the owned global::System.Diagnostics.Process.PriorityClass to a non-normal value.
Task< int?> Lifetime
The Task<TResult> resulting in the exit code of the process or null if the process was detached.
Abstraction over a global::System.Diagnostics.Process.
Task Startup
The Task representing the time until the IProcess becomes "idle".
void Terminate()
Asycnhronously terminates the process.
For waiting asynchronously.
Task Delay(TimeSpan timeSpan, CancellationToken cancellationToken)
Create a Task that completes after a given timeSpan .
DreamDaemonSecurity
DreamDaemon's security level.
EngineType
The type of engine the codebase is using.
BridgeCommandType
Represents the BridgeParameters.CommandType.
RebootState
Represents the action to take when /world/Reboot() is called.
ApiValidationStatus
Status of DMAPI validation.