Files
UnrealEngine/Engine/Source/Editor/SwarmInterface/DotNET/Util.cs
2025-05-18 13:04:45 +08:00

700 lines
18 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using AgentInterface;
namespace NSwarm
{
///////////////////////////////////////////////////////////////////////////////
/**
* A custom marshaler for strings. MarshalAs(UnmanagedType.LPTStr), cannot be used for strings
* as it always assumes UTF-16, while depending on platform, we can have UTF-16 or UTF-32.
*/
public class FStringMarshaler
{
public static IntPtr MarshalManagedToNative(String Str)
{
if (Str == null)
{
return IntPtr.Zero;
}
Byte[] Buffer = new Byte[(Str.Length + 1) * CHAR_SIZE];
#if __MonoCS__
System.Text.Encoding.UTF32.GetBytes(Str, 0, Str.Length, Buffer, 0);
#else
System.Text.Encoding.Unicode.GetBytes(Str, 0, Str.Length, Buffer, 0);
#endif
IntPtr NativeString = Marshal.AllocHGlobal(Buffer.Length);
Marshal.Copy(Buffer, 0, NativeString, Buffer.Length);
return NativeString;
}
public static String MarshalNativeToManaged(IntPtr NativeString)
{
if (NativeString == IntPtr.Zero)
{
return null;
}
Int32 Length = 0;
#if __MonoCS__
while (Marshal.ReadInt32(NativeString, Length * CHAR_SIZE) != 0)
#else
while (Marshal.ReadInt16(NativeString, Length * CHAR_SIZE) != 0)
#endif
{
Length++;
}
Byte[] Buffer = new Byte[Length * CHAR_SIZE];
Marshal.Copy(NativeString, Buffer, 0, Length * CHAR_SIZE);
#if __MonoCS__
return System.Text.Encoding.UTF32.GetString(Buffer);
#else
return System.Text.Encoding.Unicode.GetString(Buffer);
#endif
}
#if __MonoCS__
const Int32 CHAR_SIZE = 4;
#else
const Int32 CHAR_SIZE = 2;
#endif
}
/**
* A simple utility struct to manage simple interactions with GUIDs
*/
[StructLayout(LayoutKind.Sequential)]
public class FGuid
{
/**
* Default constructor, initializes to default values
*/
public FGuid()
{
A = 0;
B = 0;
C = 0;
D = 0;
}
/**
* Constructor, initializes to specified values
*/
public FGuid(UInt32 InA, UInt32 InB, UInt32 InC, UInt32 InD)
{
A = InA;
B = InB;
C = InC;
D = InD;
}
/**
* Sets the values of the GUID
*/
void Set(UInt32 InA, UInt32 InB, UInt32 InC, UInt32 InD)
{
A = InA;
B = InB;
C = InC;
D = InD;
}
/**
* Simple comparison operator
*/
public bool Equals(FGuid Other)
{
// Needed for use in Dictionary collections
return (A == Other.A) &&
(B == Other.B) &&
(C == Other.C) &&
(D == Other.D);
}
public UInt32 A;
public UInt32 B;
public UInt32 C;
public UInt32 D;
}
///////////////////////////////////////////////////////////////////////////////
/**
* A simple base class for messages. For each version of the messaging interface
* a newly derived type will inherit from this class. The base class is used to
* simply carry lightweight loads for messages, i.e. just the message type, which
* may be enough information in itself. For additional message data, subclass and
* add any additional data there.
*/
[StructLayout(LayoutKind.Sequential)]
public class FMessage
{
/**
* Default constructor, initializes to default values
*/
public FMessage()
{
Version = ESwarmVersionValue.VER_1_0;
Type = EMessageType.NONE;
}
/**
* Constructor, initializes to specified values
*
* @param NewType The type of the message, one of EMessageType
*/
public FMessage(EMessageType NewType)
{
Version = ESwarmVersionValue.VER_1_0;
Type = NewType;
}
/**
* Constructor, initializes to specified values
*
* @param NewVersion The version of the message format; one of ESwarmVersionValue
* @param NewType The type of the message, one of EMessageType
*/
public FMessage(ESwarmVersionValue NewVersion, EMessageType NewType)
{
Version = NewVersion;
Type = NewType;
}
/** The version of the message format; one of ESwarmVersionValue */
public ESwarmVersionValue Version;
/** The type of the message, one of EMessageType */
public EMessageType Type;
}
///////////////////////////////////////////////////////////////////////////////
/**
* Implementation of a generic info message, which just includes generic text.
*/
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class FInfoMessage : FMessage
{
public FInfoMessage()
: base(ESwarmVersionValue.VER_1_0, EMessageType.INFO)
{
}
/**
* Constructor, initializes to default and specified values
*/
public FInfoMessage(String InTextMessage)
: base(ESwarmVersionValue.VER_1_0, EMessageType.INFO)
{
TextMessage = FStringMarshaler.MarshalManagedToNative(InTextMessage);
}
/** Generic text message for informational purposes */
public IntPtr TextMessage;
}
///////////////////////////////////////////////////////////////////////////////
/**
* Implementation of a alert message, which includes:
*
* - The alert type:
* a) warning
* b) error
* c) critical error
* - The Job GUID
* - The GUID of the item causing the issue
* - A 32-bit field intended to identify the type of the item
* - A string giving the issue message (which will be localized on the Unreal side of things).
*/
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class FAlertMessage : FMessage
{
public FAlertMessage()
: base(ESwarmVersionValue.VER_1_0, EMessageType.ALERT)
{
}
/**
* Constructor, initializes to default and specified values
*/
public FAlertMessage(FGuid InJobGuid, EAlertLevel InAlertLevel, FGuid InObjectGuid, int InTypeId)
: base(ESwarmVersionValue.VER_1_0, EMessageType.ALERT)
{
JobGuid = InJobGuid;
AlertLevel = InAlertLevel;
ObjectGuid = InObjectGuid;
TypeId = InTypeId;
TextMessage = IntPtr.Zero;
}
/**
* Constructor, initializes to default and specified values
*/
public FAlertMessage( FGuid InJobGuid, EAlertLevel InAlertLevel, FGuid InObjectGuid, int InTypeId, String InTextMessage )
: base(ESwarmVersionValue.VER_1_0, EMessageType.ALERT)
{
JobGuid = InJobGuid;
AlertLevel = InAlertLevel;
ObjectGuid = InObjectGuid;
TypeId = InTypeId;
TextMessage = FStringMarshaler.MarshalManagedToNative(InTextMessage);
}
/** The Job Guid */
public FGuid JobGuid;
/** The type of alert */
public EAlertLevel AlertLevel;
/** The identifier for the object that is associated with the issue */
public FGuid ObjectGuid;
/** App-specific identifier for the type of the object */
public int TypeId;
/** Generic text message for informational purposes */
public IntPtr TextMessage;
};
///////////////////////////////////////////////////////////////////////////////
/**
* Implementation of a generic info message, which just includes generic text.
*/
[StructLayout(LayoutKind.Sequential)]
public class FTimingMessage : FMessage
{
public FTimingMessage()
: base(ESwarmVersionValue.VER_1_0, EMessageType.TIMING)
{
}
/**
* Constructor, initializes to default and specified values
*/
public FTimingMessage( EProgressionState NewState, int InThreadNum )
: base(ESwarmVersionValue.VER_1_0, EMessageType.TIMING)
{
State = NewState;
ThreadNum = InThreadNum;
}
/** State that the distributed job is transitioning to */
public EProgressionState State;
/** The thread this state is referring to */
public int ThreadNum;
};
///////////////////////////////////////////////////////////////////////////////
/**
* Implementation of a task request response message. All uses include the GUID
* of the Job the request referred to. Currently used for these message types:
*
* TASK_RELEASE: Signifies that the requester is no longer required to process
* any more Tasks. The requester is free to consider this Job completed.
*
* TASK_RESERVATION: Sent back only if the Job specified is still active but
* no additional Tasks are available at this time.
*
* TASK_SPECIFICATION: Details a Task that can be worked on
*/
[StructLayout(LayoutKind.Sequential)]
public class FTaskRequestResponse : FMessage
{
public FTaskRequestResponse()
: base(ESwarmVersionValue.VER_1_0, EMessageType.TASK_REQUEST_RESPONSE)
{
}
/**
* Constructor, initializes to default and specified values
*/
public FTaskRequestResponse( ETaskRequestResponseType NewResponseType )
: base(ESwarmVersionValue.VER_1_0, EMessageType.TASK_REQUEST_RESPONSE)
{
ResponseType = NewResponseType;
}
/** The type of response this message is. Subclasses add any additional data */
ETaskRequestResponseType ResponseType;
};
///////////////////////////////////////////////////////////////////////////////
/**
* Encapsulates information about a Job specification passed into BeginJobSpecification
*/
public class FJobSpecification
{
/**
* Default constructor, initializes to an empty (invalid) job.
*/
public FJobSpecification()
{
ExecutableName = null;
Parameters = null;
Flags = EJobTaskFlags.TASK_FLAG_USE_DEFAULTS;
RequiredDependencies = null;
RequiredDependencyCount = 0;
OptionalDependencies = null;
OptionalDependencyCount = 0;
DescriptionKeys = null;
DescriptionValues = null;
DescriptionCount = 0;
}
/**
* Constructor, initializes to default and specified values
*/
public FJobSpecification(String JobExecutableName, String JobParameters, EJobTaskFlags JobFlags)
{
ExecutableName = JobExecutableName;
Parameters = JobParameters;
Flags = JobFlags;
RequiredDependencies = null;
RequiredDependencyCount = 0;
OptionalDependencies = null;
OptionalDependencyCount = 0;
DescriptionKeys = null;
DescriptionValues = null;
DescriptionCount = 0;
}
/**
* Used to add channel dependencies to a Job. When an Agent runs this Job,
* it will ensure that all dependencies are satisfied prior to launching
* the executable. Note that the Job executable is an implied dependency.
*
* @param NewRequiredDependencies The list of additional required dependent channel names
* @param NewRequiredDependencyCount The number of elements in the NewRequiredDependencies list
* @param NewOptionalDependencies The list of additional optional dependent channel names
* @param NewOptionalDependencyCount The number of elements in the NewOptionalDependencies list
*/
void AddDependencies(String[] NewRequiredDependencies, UInt32 NewRequiredDependencyCount, String[] NewOptionalDependencies, UInt32 NewOptionalDependencyCount)
{
RequiredDependencies = NewRequiredDependencies;
RequiredDependencyCount = NewRequiredDependencyCount;
OptionalDependencies = NewOptionalDependencies;
OptionalDependencyCount = NewOptionalDependencyCount;
}
void AddDescription(String[] NewDescriptionKeys, String[] NewDescriptionValues, UInt32 NewDescriptionCount )
{
DescriptionKeys = NewDescriptionKeys;
DescriptionValues = NewDescriptionValues;
DescriptionCount = NewDescriptionCount;
}
/** The Job's executable name and parameter string */
public String ExecutableName;
public String Parameters;
/** Flags used to control the behavior of the executing Job */
public EJobTaskFlags Flags;
/** Any additional Job dependencies */
public String[] RequiredDependencies;
public UInt32 RequiredDependencyCount;
public String[] OptionalDependencies;
public UInt32 OptionalDependencyCount;
/** Optional Job description values in key/value form */
public String[] DescriptionKeys;
public String[] DescriptionValues;
public UInt32 DescriptionCount;
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
class FJobSpecificationMarshalHelper
{
public IntPtr ExecutableName;
public IntPtr Parameters;
public EJobTaskFlags Flags;
public IntPtr RequiredDependencies;
public UInt32 RequiredDependencyCount;
public IntPtr OptionalDependencies;
public UInt32 OptionalDependencyCount;
public IntPtr DescriptionKeys;
public IntPtr DescriptionValues;
public UInt32 DescriptionCount;
}
///////////////////////////////////////////////////////////////////////////////
/**
* Encapsulates information about a Task specification passed into AddTask and
* later sent in response to a TASK_REQUEST message
*/
public class FTaskSpecification : FTaskRequestResponse
{
/**
* Constructor, initializes to default and specified values
*/
public FTaskSpecification(FGuid TaskTaskGuid, String TaskParameters, EJobTaskFlags TaskFlags)
: base(ETaskRequestResponseType.SPECIFICATION)
{
TaskGuid = TaskTaskGuid;
Parameters = TaskParameters;
Flags = TaskFlags;
Dependencies = null;
DependencyCount = 0;
}
/**
* Used to add channel dependencies to a Task. When an Agent runs this Task,
* it will ensure that all dependencies are satisfied prior to giving the
* Task to the requester.
*
* @param NewDependencies The list of additional dependent channel names
* @param NewDependencyCount The number of elements in the NewDependencies list
*/
void AddDependencies(String[] NewDependencies, UInt32 NewDependencyCount )
{
Dependencies = NewDependencies;
DependencyCount = NewDependencyCount;
}
/** The GUID used for identifying the Task being referred to */
public FGuid TaskGuid;
/** The Task's parameter string specified with AddTask */
public String Parameters;
/** Flags used to control the behavior of the Task, subject to overrides from the containing Job */
public EJobTaskFlags Flags;
/** The Task's cost, relative to all other Tasks in the same Job, used for even distribution and scheduling */
public UInt32 Cost;
/** Any additional Task dependencies */
public String[] Dependencies;
public UInt32 DependencyCount;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class FTaskSpecificationMarshalHelper : FTaskRequestResponse
{
public FTaskSpecificationMarshalHelper()
: base(ETaskRequestResponseType.SPECIFICATION)
{
}
public FGuid TaskGuid;
public IntPtr Parameters;
public EJobTaskFlags Flags;
public UInt32 Cost;
public IntPtr Dependencies;
public UInt32 DependencyCount;
}
///////////////////////////////////////////////////////////////////////////////
/**
* Encapsulates information about a Job's state, used to communicate
* back to the Instigator
*/
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class FJobState : FMessage
{
public FJobState()
: base(ESwarmVersionValue.VER_1_0, EMessageType.JOB_STATE)
{
}
/**
* Constructor, initializes to specified values
*/
public FJobState(FGuid NewJobGuid, EJobTaskState NewJobState)
: base(ESwarmVersionValue.VER_1_0, EMessageType.JOB_STATE)
{
JobGuid = NewJobGuid;
JobState = NewJobState;
JobMessage = IntPtr.Zero;
JobExitCode = 0;
JobRunningTime = 0.0;
}
/** The Job GUID used for identifying the Job */
public FGuid JobGuid;
/** The current state and arbitrary message */
public EJobTaskState JobState;
public IntPtr JobMessage;
/** Various stats, including run time, exit codes, etc. */
public Int32 JobExitCode;
public double JobRunningTime;
}
///////////////////////////////////////////////////////////////////////////////
/**
* Encapsulates information about a Task's state, used to communicate
* back to the Instigator
*/
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class FTaskState : FMessage
{
public FTaskState()
: base(ESwarmVersionValue.VER_1_0, EMessageType.TASK_STATE)
{
}
/**
* Constructor, initializes to specified values
*/
public FTaskState(FGuid NewTaskGuid, EJobTaskState NewTaskState)
: base(ESwarmVersionValue.VER_1_0, EMessageType.TASK_STATE)
{
TaskGuid = NewTaskGuid;
TaskState = NewTaskState;
TaskMessage = IntPtr.Zero;
TaskExitCode = 0;
TaskRunningTime = 0.0;
}
/** The Task GUID used for identifying the Task */
public FGuid TaskGuid;
/** The current Task state and arbitrary message */
public EJobTaskState TaskState;
public IntPtr TaskMessage;
/** Various stats, including run time, exit codes, etc. */
public Int32 TaskExitCode;
public double TaskRunningTime;
}
///////////////////////////////////////////////////////////////////////////////
/**
* A thread-safe reader-writer-locked Dictionary wrapper
*/
public class ReaderWriterDictionary<TKey, TValue>
{
/**
* The key to protecting the contained dictionary properly
*/
ReaderWriterLock AccessLock;
/**
* The protected dictionary
*/
Dictionary<TKey, TValue> ProtectedDictionary;
public ReaderWriterDictionary()
{
AccessLock = new ReaderWriterLock();
ProtectedDictionary = new Dictionary<TKey, TValue>();
}
/**
* Wrappers around each method we need to protect in the dictionary
*/
public void Add(TKey Key, TValue Value)
{
// Modifies the collection, use a writer lock
AccessLock.AcquireWriterLock(Timeout.Infinite);
try
{
ProtectedDictionary.Add(Key, Value);
}
finally
{
AccessLock.ReleaseWriterLock();
}
}
public bool Remove(TKey Key)
{
// Modifies the collection, use a writer lock
AccessLock.AcquireWriterLock(Timeout.Infinite);
bool ReturnValue = false;
try
{
ReturnValue = ProtectedDictionary.Remove(Key);
}
finally
{
AccessLock.ReleaseWriterLock();
}
return ReturnValue;
}
public void Clear()
{
// Modifies the collection, use a writer lock
AccessLock.AcquireWriterLock(Timeout.Infinite);
try
{
ProtectedDictionary.Clear();
}
finally
{
AccessLock.ReleaseWriterLock();
}
}
public bool TryGetValue(TKey Key, out TValue Value)
{
// Does not modify the collection, use a reader lock
AccessLock.AcquireReaderLock(Timeout.Infinite);
bool ReturnValue = false;
try
{
ReturnValue = ProtectedDictionary.TryGetValue(Key, out Value);
}
finally
{
AccessLock.ReleaseReaderLock();
}
return ReturnValue;
}
public Dictionary<TKey, TValue>.ValueCollection Values
{
get
{
AccessLock.AcquireReaderLock(Timeout.Infinite);
Dictionary<TKey, TValue>.ValueCollection CopyOfValues = null;
try
{
CopyOfValues = new Dictionary<TKey, TValue>.ValueCollection(ProtectedDictionary);
}
finally
{
AccessLock.ReleaseReaderLock();
}
return CopyOfValues;
}
private set
{
Values = value;
}
}
public Int32 Count
{
get
{
// Does not modify the collection, use a reader lock
AccessLock.AcquireReaderLock(Timeout.Infinite);
Int32 ReturnValue = 0;
try
{
ReturnValue = ProtectedDictionary.Count;
}
finally
{
AccessLock.ReleaseReaderLock();
}
return ReturnValue;
}
}
}
}