Implement proper FName / FNamePool
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
namespace Prospect.Unreal.Core.Names;
|
||||||
|
|
||||||
|
public enum EFindName
|
||||||
|
{
|
||||||
|
/** Find a name; return 0 if it doesn't exist. */
|
||||||
|
FNAME_Find,
|
||||||
|
|
||||||
|
/** Find a name or add it if it doesn't exist. */
|
||||||
|
FNAME_Add,
|
||||||
|
|
||||||
|
/** Finds a name and replaces it. Adds it if missing. This is only used by UHT and is generally not safe for threading.
|
||||||
|
* All this really is used for is correcting the case of names. In MT conditions you might get a half-changed name.
|
||||||
|
*/
|
||||||
|
FNAME_Replace_Not_Safe_For_Threading,
|
||||||
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
namespace Prospect.Unreal.Core.Names
|
namespace Prospect.Unreal.Core.Names
|
||||||
{
|
{
|
||||||
public enum UnrealNameKey
|
public enum EName
|
||||||
{
|
{
|
||||||
None = 0,
|
None = 0,
|
||||||
|
|
||||||
@@ -1,36 +1,150 @@
|
|||||||
namespace Prospect.Unreal.Core.Names;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
public class FName
|
namespace Prospect.Unreal.Core.Names;
|
||||||
|
|
||||||
|
public readonly struct FName
|
||||||
{
|
{
|
||||||
public FName()
|
public FName() : this(EName.None, FNameHelper.NAME_NO_NUMBER_INTERNAL)
|
||||||
{
|
{
|
||||||
NameIndex = -1; // ?
|
|
||||||
Number = -1; // ?
|
|
||||||
Str = "None";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public FName(string str)
|
/// <summary>
|
||||||
|
/// Create an FName with a hardcoded string index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hardcodedName">The hardcoded value the string portion of the name will have</param>
|
||||||
|
public FName(EName hardcodedName) : this(hardcodedName, FNameHelper.NAME_NO_NUMBER_INTERNAL)
|
||||||
{
|
{
|
||||||
NameIndex = -1;
|
|
||||||
Number = -1;
|
|
||||||
Str = str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public FName(string str, int number)
|
/// <summary>
|
||||||
|
/// Create an FName with a hardcoded string index and (instance).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hardcodedName">The hardcoded value the string portion of the name will have</param>
|
||||||
|
/// <param name="number">The hardcoded value for the number portion of the name</param>
|
||||||
|
public FName(EName hardcodedName, int number)
|
||||||
{
|
{
|
||||||
NameIndex = -1;
|
Index = FNamePool.Find(hardcodedName);
|
||||||
Number = number;
|
Number = (uint)number;
|
||||||
Str = str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Int32 NameIndex { get; set; }
|
/// <summary>
|
||||||
|
/// Create an FName. If FindType is FNAME_Find, and the string part of the name
|
||||||
|
/// doesn't already exist, then the name will be NAME_None.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Value for the string portion of the name</param>
|
||||||
|
/// <param name="findType">Action to take</param>
|
||||||
|
public FName(string value, EFindName findType = EFindName.FNAME_Add)
|
||||||
|
{
|
||||||
|
var name = FNameHelper.MakeDetectNumber(value, findType);
|
||||||
|
|
||||||
public Int32 Number { get; set; }
|
Index = name.Index;
|
||||||
|
Number = name.Number;
|
||||||
|
}
|
||||||
|
|
||||||
public string Str { get; set; }
|
/// <summary>
|
||||||
|
/// Create an FName. If FindType is FNAME_Find, and the string part of the name
|
||||||
|
/// doesn't already exist, then the name will be NAME_None.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Value for the string portion of the name</param>
|
||||||
|
/// <param name="number">Value for the number portion of the name</param>
|
||||||
|
/// <param name="findType">Action to take</param>
|
||||||
|
public FName(string value, int number, EFindName findType = EFindName.FNAME_Add)
|
||||||
|
{
|
||||||
|
var name = FNameHelper.MakeWithNumber(value, findType, number);
|
||||||
|
|
||||||
|
Index = name.Index;
|
||||||
|
Number = name.Number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Only use this if you know what you are doing (:
|
||||||
|
/// </summary>
|
||||||
|
public FName(FNameEntryId index, int number)
|
||||||
|
{
|
||||||
|
Index = index;
|
||||||
|
Number = (uint)number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Index of the name
|
||||||
|
/// </summary>
|
||||||
|
public FNameEntryId Index { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number portion of the string/number pair
|
||||||
|
/// (stored internally as 1 more than actual, so zero'd memory will be the default, no-instance case)
|
||||||
|
/// </summary>
|
||||||
|
public uint Number { get; }
|
||||||
|
|
||||||
|
public static implicit operator FName(EName name)
|
||||||
|
{
|
||||||
|
return new FName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public int GetNumber()
|
||||||
|
{
|
||||||
|
return (int)Number;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetPlainNameString()
|
||||||
|
{
|
||||||
|
return FNamePool.Resolve(Index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EName? ToEName()
|
||||||
|
{
|
||||||
|
return FNamePool.FindEName(Index);
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return Str;
|
var name = GetPlainNameString();
|
||||||
|
|
||||||
|
if (Number == FNameHelper.NAME_NO_NUMBER_INTERNAL)
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{name}_{FNameHelper.NAME_INTERNAL_TO_EXTERNAL(GetNumber())}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(FName left, EName right)
|
||||||
|
{
|
||||||
|
return (left.Index == right) & (left.GetNumber() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(FName left, EName right)
|
||||||
|
{
|
||||||
|
return (left.Index != right) | (left.GetNumber() != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(FName left, FName right)
|
||||||
|
{
|
||||||
|
return (left.Index == right.Index) & (left.GetNumber() == right.GetNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(FName left, FName right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(FName other)
|
||||||
|
{
|
||||||
|
return this == other;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return obj is FName other && Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
return (Index.GetHashCode() * 397) ^ (int)Number;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
namespace Prospect.Unreal.Core.Names;
|
||||||
|
|
||||||
|
public readonly struct FNameEntryId
|
||||||
|
{
|
||||||
|
public FNameEntryId()
|
||||||
|
{
|
||||||
|
Value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FNameEntryId(uint value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly uint Value;
|
||||||
|
|
||||||
|
public static bool operator ==(FNameEntryId left, EName right)
|
||||||
|
{
|
||||||
|
return left == FNamePool.Find(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(FNameEntryId left, EName right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(FNameEntryId left, FNameEntryId right)
|
||||||
|
{
|
||||||
|
return left.Value == right.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(FNameEntryId left, FNameEntryId right)
|
||||||
|
{
|
||||||
|
return left.Value != right.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(FNameEntryId other)
|
||||||
|
{
|
||||||
|
return this == other;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return obj is FNameEntryId other && Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return (int)Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Prospect.Unreal.Core.Names;
|
||||||
|
|
||||||
|
internal static class FNameHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Externally, the instance number to represent no instance number is NAME_NO_NUMBER,
|
||||||
|
/// but internally, we add 1 to indices, so we use this #define internally for
|
||||||
|
/// zero'd memory initialization will still make NAME_None as expected
|
||||||
|
/// </summary>
|
||||||
|
public const int NAME_NO_NUMBER_INTERNAL = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Special value for an FName with no number
|
||||||
|
/// </summary>
|
||||||
|
public const int NAME_NO_NUMBER = NAME_NO_NUMBER_INTERNAL - 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum size of name.
|
||||||
|
/// </summary>
|
||||||
|
public const int NAME_SIZE = 1024;
|
||||||
|
|
||||||
|
public static FName MakeDetectNumber(string name, EFindName findType)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
return new FName();
|
||||||
|
}
|
||||||
|
|
||||||
|
var nameLen = name.Length;
|
||||||
|
var internalNumber = ParseNumber(name, ref nameLen);
|
||||||
|
return MakeWithNumber(name.Substring(0, nameLen), findType, (int)internalNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint ParseNumber(ReadOnlySpan<char> name, ref int nameLength)
|
||||||
|
{
|
||||||
|
var len = nameLength;
|
||||||
|
var digits = 0;
|
||||||
|
|
||||||
|
for (var i = len - 1; i >= 0 && name[i] >= '0' && name[i] <= '9'; --i)
|
||||||
|
{
|
||||||
|
++digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstDigit = len - digits;
|
||||||
|
const int maxDigitsInt32 = 10;
|
||||||
|
if (digits != 0 && digits < len && name[firstDigit - 1] == '_' && digits <= maxDigitsInt32)
|
||||||
|
{
|
||||||
|
// check for the case where there are multiple digits after the _ and the first one
|
||||||
|
// is a 0 ("Rocket_04"). Can't split this case. (So, we check if the first char
|
||||||
|
// is not 0 or the length of the number is 1 (since ROcket_0 is valid)
|
||||||
|
if (digits == 1 || name[firstDigit] != '0')
|
||||||
|
{
|
||||||
|
var number = long.Parse(name.Slice(len - digits, digits));
|
||||||
|
if (number < int.MaxValue)
|
||||||
|
{
|
||||||
|
nameLength -= 1 + digits;
|
||||||
|
return NAME_EXTERNAL_TO_INTERNAL((uint)number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NAME_NO_NUMBER_INTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FName MakeWithNumber(string name, EFindName findType, int internalNumber)
|
||||||
|
{
|
||||||
|
if (name.Length == 0)
|
||||||
|
{
|
||||||
|
return new FName();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Make(name, findType, internalNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FName Make(string name, EFindName findType, int internalNumber)
|
||||||
|
{
|
||||||
|
if (name.Length >= NAME_SIZE)
|
||||||
|
{
|
||||||
|
return new FName("ERROR_NAME_SIZE_EXCEEDED");
|
||||||
|
}
|
||||||
|
|
||||||
|
FNameEntryId index;
|
||||||
|
|
||||||
|
if (findType == EFindName.FNAME_Add)
|
||||||
|
{
|
||||||
|
index = FNamePool.Store(name);
|
||||||
|
}
|
||||||
|
else if (findType == EFindName.FNAME_Find)
|
||||||
|
{
|
||||||
|
index = FNamePool.Find(name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FName(index, internalNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static uint NAME_EXTERNAL_TO_INTERNAL(uint number)
|
||||||
|
{
|
||||||
|
return number + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static int NAME_EXTERNAL_TO_INTERNAL(int number)
|
||||||
|
{
|
||||||
|
return number + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static uint NAME_INTERNAL_TO_EXTERNAL(uint number)
|
||||||
|
{
|
||||||
|
return number - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static int NAME_INTERNAL_TO_EXTERNAL(int number)
|
||||||
|
{
|
||||||
|
return number - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
namespace Prospect.Unreal.Core.Names;
|
||||||
|
|
||||||
|
public static class FNamePool
|
||||||
|
{
|
||||||
|
private static readonly object NamesLock = new object();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Map hardcoded names in <see cref="UnrealNames"/>.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Dictionary<EName, FNameEntryId> HardcodedNames = new Dictionary<EName, FNameEntryId>();
|
||||||
|
private static readonly Dictionary<FNameEntryId, EName> HardcodedNamesReverse = new Dictionary<FNameEntryId, EName>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Map existing strings to a <see cref="FName.Index"/>.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Dictionary<string, FNameEntryId> Names = new Dictionary<string, FNameEntryId>();
|
||||||
|
private static readonly Dictionary<FNameEntryId, string> NamesReverse = new Dictionary<FNameEntryId, string>();
|
||||||
|
|
||||||
|
private static uint _counter;
|
||||||
|
|
||||||
|
static FNamePool()
|
||||||
|
{
|
||||||
|
// Initialize hardcoded names.
|
||||||
|
foreach (var (key, value) in UnrealNames.Names)
|
||||||
|
{
|
||||||
|
var index = Store(value);
|
||||||
|
HardcodedNames[key] = index;
|
||||||
|
HardcodedNamesReverse[index] = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EName? FindEName(FNameEntryId index)
|
||||||
|
{
|
||||||
|
if (HardcodedNamesReverse.TryGetValue(index, out var result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FNameEntryId Find(string name)
|
||||||
|
{
|
||||||
|
if (Names.TryGetValue(name, out var result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FNameEntryId((uint)EName.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FNameEntryId Find(EName name)
|
||||||
|
{
|
||||||
|
return HardcodedNames[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Resolve(FNameEntryId index)
|
||||||
|
{
|
||||||
|
return NamesReverse[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FNameEntryId Store(ReadOnlySpan<char> valueSpan)
|
||||||
|
{
|
||||||
|
var value = valueSpan.ToString();
|
||||||
|
|
||||||
|
if (Names.TryGetValue(value, out var result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (NamesLock)
|
||||||
|
{
|
||||||
|
// Check again incase a previous lock added it.
|
||||||
|
if (Names.TryGetValue(value, out result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new FName.
|
||||||
|
result = new FNameEntryId(_counter++);
|
||||||
|
|
||||||
|
// Store FName.
|
||||||
|
Names[value] = result;
|
||||||
|
NamesReverse[result] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
namespace Prospect.Unreal.Core.Names
|
namespace Prospect.Unreal.Core.Names
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Hardcoded names in Unreal Engine 4.26.2. See "UnrealNames.inl"
|
||||||
|
/// </summary>
|
||||||
public static class UnrealNames
|
public static class UnrealNames
|
||||||
{
|
{
|
||||||
public const int MaxNetworkedHardcodedName = 410;
|
public const int MaxNetworkedHardcodedName = 410;
|
||||||
@@ -217,15 +220,11 @@ namespace Prospect.Unreal.Core.Names
|
|||||||
names.Add(702, "Root");
|
names.Add(702, "Root");
|
||||||
|
|
||||||
// Save.
|
// Save.
|
||||||
Names = names;
|
Names = names.ToDictionary(x => (EName) x.Key, y => y.Value);
|
||||||
FNames = Names.ToDictionary(x => (UnrealNameKey) x.Key, y => new FName(y.Value, y.Key));
|
MaxHardcodedNameIndex = (int)Names.Last().Key + 1;
|
||||||
MaxHardcodedNameIndex = Names.Last().Key + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IReadOnlyDictionary<int, string> Names { get; }
|
public static IReadOnlyDictionary<EName, string> Names { get; }
|
||||||
|
|
||||||
public static IReadOnlyDictionary<UnrealNameKey, FName> FNames { get; }
|
|
||||||
|
|
||||||
public static int MaxHardcodedNameIndex { get; }
|
public static int MaxHardcodedNameIndex { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ public class UActorChannel : UChannel
|
|||||||
public UActorChannel()
|
public UActorChannel()
|
||||||
{
|
{
|
||||||
ChType = EChannelType.CHTYPE_Actor;
|
ChType = EChannelType.CHTYPE_Actor;
|
||||||
ChName = UnrealNames.FNames[UnrealNameKey.Actor];
|
ChName = EName.Actor;
|
||||||
// bClearRecentActorRefs = true;
|
// bClearRecentActorRefs = true;
|
||||||
// bHoldQueuedExportBunchesAndGUIDs = false;
|
// bHoldQueuedExportBunchesAndGUIDs = false;
|
||||||
// QueuedCloseReason = EChannelCloseReason::Destroyed;
|
// QueuedCloseReason = EChannelCloseReason::Destroyed;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public class UControlChannel : UChannel
|
|||||||
public UControlChannel()
|
public UControlChannel()
|
||||||
{
|
{
|
||||||
ChType = EChannelType.CHTYPE_Control;
|
ChType = EChannelType.CHTYPE_Control;
|
||||||
ChName = UnrealNames.FNames[UnrealNameKey.Control];
|
ChName = EName.Control;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ReceivedBunch(FInBunch bunch)
|
protected override void ReceivedBunch(FInBunch bunch)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public class UVoiceChannel : UChannel
|
|||||||
public UVoiceChannel()
|
public UVoiceChannel()
|
||||||
{
|
{
|
||||||
ChType = EChannelType.CHTYPE_Voice;
|
ChType = EChannelType.CHTYPE_Voice;
|
||||||
ChName = UnrealNames.FNames[UnrealNameKey.Control];
|
ChName = EName.Voice;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ReceivedBunch(FInBunch bunch)
|
protected override void ReceivedBunch(FInBunch bunch)
|
||||||
|
|||||||
@@ -71,12 +71,7 @@ public class FUniqueNetIdRepl
|
|||||||
{
|
{
|
||||||
var typeString = ar.ReadString();
|
var typeString = ar.ReadString();
|
||||||
var type = new FName(typeString);
|
var type = new FName(typeString);
|
||||||
// TODO: Add FName into FNamePool
|
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
if (ar.IsError() || type.Number == (int)UnrealNameKey.None)
|
|
||||||
{
|
|
||||||
bValidTypeHash = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -110,7 +105,7 @@ public class FUniqueNetIdRepl
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
type = UnrealNames.FNames[UnrealNameKey.None];
|
type = new FName(EName.None);
|
||||||
// TODO: Type = UOnlineEngineInterface::Get()->GetSubsystemFromReplicationHash(TypeHash);
|
// TODO: Type = UOnlineEngineInterface::Get()->GetSubsystemFromReplicationHash(TypeHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ namespace Prospect.Unreal.Net.Packets.Bunch
|
|||||||
Connection = inConnection;
|
Connection = inConnection;
|
||||||
ChIndex = 0;
|
ChIndex = 0;
|
||||||
ChType = EChannelType.CHTYPE_None;
|
ChType = EChannelType.CHTYPE_None;
|
||||||
ChName = UnrealNames.FNames[UnrealNameKey.None];
|
ChName = EName.None;
|
||||||
ChSequence = 0;
|
ChSequence = 0;
|
||||||
bOpen = false;
|
bOpen = false;
|
||||||
bClose = false;
|
bClose = false;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public class FOutBunch : FNetBitWriter
|
|||||||
{
|
{
|
||||||
public FOutBunch() : base(0)
|
public FOutBunch() : base(0)
|
||||||
{
|
{
|
||||||
ChName = UnrealNames.FNames[UnrealNameKey.None];
|
ChName = EName.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FOutBunch(UChannel inChannel, bool bInClose) : base(
|
public FOutBunch(UChannel inChannel, bool bInClose) : base(
|
||||||
@@ -53,7 +53,7 @@ public class FOutBunch : FNetBitWriter
|
|||||||
Time = 0;
|
Time = 0;
|
||||||
ChIndex = 0;
|
ChIndex = 0;
|
||||||
ChType = EChannelType.CHTYPE_None;
|
ChType = EChannelType.CHTYPE_None;
|
||||||
ChName = UnrealNames.FNames[UnrealNameKey.None];
|
ChName = EName.None;
|
||||||
ChSequence = 0;
|
ChSequence = 0;
|
||||||
PacketId = 0;
|
PacketId = 0;
|
||||||
ReceivedAck = false;
|
ReceivedAck = false;
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ public abstract class UNetConnection : UPlayer
|
|||||||
public UNetConnection()
|
public UNetConnection()
|
||||||
{
|
{
|
||||||
_channelsToTick = new HashSet<UChannel>();
|
_channelsToTick = new HashSet<UChannel>();
|
||||||
_playerOnlinePlatformName = UnrealNames.FNames[UnrealNameKey.None];
|
_playerOnlinePlatformName = EName.None;
|
||||||
_packetOrderCache = null;
|
_packetOrderCache = null;
|
||||||
_packetOrderCacheStartIdx = 0;
|
_packetOrderCacheStartIdx = 0;
|
||||||
_packetOrderCacheCount = 0;
|
_packetOrderCacheCount = 0;
|
||||||
@@ -649,13 +649,13 @@ public abstract class UNetConnection : UPlayer
|
|||||||
switch (bunch.ChType)
|
switch (bunch.ChType)
|
||||||
{
|
{
|
||||||
case EChannelType.CHTYPE_Control:
|
case EChannelType.CHTYPE_Control:
|
||||||
bunch.ChName = UnrealNames.FNames[UnrealNameKey.Control];
|
bunch.ChName = EName.Control;
|
||||||
break;
|
break;
|
||||||
case EChannelType.CHTYPE_Voice:
|
case EChannelType.CHTYPE_Voice:
|
||||||
bunch.ChName = UnrealNames.FNames[UnrealNameKey.Voice];
|
bunch.ChName = EName.Voice;
|
||||||
break;
|
break;
|
||||||
case EChannelType.CHTYPE_Actor:
|
case EChannelType.CHTYPE_Actor:
|
||||||
bunch.ChName = UnrealNames.FNames[UnrealNameKey.Actor];
|
bunch.ChName = EName.Actor;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -667,32 +667,30 @@ public abstract class UNetConnection : UPlayer
|
|||||||
|
|
||||||
if (!UPackageMap.StaticSerializeName(reader, ref chName) || reader.IsError())
|
if (!UPackageMap.StaticSerializeName(reader, ref chName) || reader.IsError())
|
||||||
{
|
{
|
||||||
// TODO: Close connection
|
Close();
|
||||||
Logger.Fatal("Channel name serialization failed");
|
Logger.Fatal("Channel name serialization failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bunch.ChName = chName;
|
bunch.ChName = chName!.Value;
|
||||||
|
|
||||||
switch ((UnrealNameKey) bunch.ChName.Number)
|
if (bunch.ChName == EName.Control)
|
||||||
{
|
{
|
||||||
case UnrealNameKey.Control:
|
|
||||||
bunch.ChType = EChannelType.CHTYPE_Control;
|
bunch.ChType = EChannelType.CHTYPE_Control;
|
||||||
break;
|
}
|
||||||
|
else if (bunch.ChName == EName.Voice)
|
||||||
case UnrealNameKey.Voice:
|
{
|
||||||
bunch.ChType = EChannelType.CHTYPE_Voice;
|
bunch.ChType = EChannelType.CHTYPE_Voice;
|
||||||
break;
|
}
|
||||||
|
else if (bunch.ChName == EName.Actor)
|
||||||
case UnrealNameKey.Actor:
|
{
|
||||||
bunch.ChType = EChannelType.CHTYPE_Actor;
|
bunch.ChType = EChannelType.CHTYPE_Actor;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bunch.ChType = EChannelType.CHTYPE_None;
|
bunch.ChType = EChannelType.CHTYPE_None;
|
||||||
bunch.ChName = UnrealNames.FNames[UnrealNameKey.None];
|
bunch.ChName = EName.None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -700,12 +698,11 @@ public abstract class UNetConnection : UPlayer
|
|||||||
|
|
||||||
// If there's an existing channel and the bunch specified it's channel type, make sure they match.
|
// If there's an existing channel and the bunch specified it's channel type, make sure they match.
|
||||||
if (channel != null &&
|
if (channel != null &&
|
||||||
(bunch.ChName.Number != (int)UnrealNameKey.None) &&
|
(bunch.ChName != EName.None) &&
|
||||||
(bunch.ChName.Number != channel.ChName.Number))
|
(bunch.ChName != channel.ChName))
|
||||||
{
|
{
|
||||||
Logger.Error("Existing channel at index {ChIndex} with type \"{ChName}\" differs from the incoming bunch's expected channel type, \"{BunchChName}\"",
|
Logger.Error("Existing channel at index {ChIndex} with type \"{ChName}\" differs from the incoming bunch's expected channel type, \"{BunchChName}\"", bunch.ChIndex, channel.ChName, bunch.ChName);
|
||||||
bunch.ChIndex, channel.ChName.Str, bunch.ChName.Str);
|
Close();
|
||||||
// TODO: Close();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,7 +711,7 @@ public abstract class UNetConnection : UPlayer
|
|||||||
if (reader.IsError())
|
if (reader.IsError())
|
||||||
{
|
{
|
||||||
Logger.Error("Bunch header overflow");
|
Logger.Error("Bunch header overflow");
|
||||||
// TODO: Close();
|
Close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,27 +743,27 @@ public abstract class UNetConnection : UPlayer
|
|||||||
Logger.Verbose(" bOpen Bunch, Channel {Ch} Sequence {Seq}: Size {A:###.0}+{B:###.0}", bunch.ChIndex, bunch.ChSequence, (headerPos - incomingStartPos)/8.0f, (reader.GetPosBits()-headerPos)/8.0f);
|
Logger.Verbose(" bOpen Bunch, Channel {Ch} Sequence {Seq}: Size {A:###.0}+{B:###.0}", bunch.ChIndex, bunch.ChSequence, (headerPos - incomingStartPos)/8.0f, (reader.GetPosBits()-headerPos)/8.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Channels[bunch.ChIndex] == null && (bunch.ChIndex != 0 || bunch.ChName != UnrealNames.FNames[UnrealNameKey.Control]))
|
if (Channels[bunch.ChIndex] == null && (bunch.ChIndex != 0 || bunch.ChName != EName.Control))
|
||||||
{
|
{
|
||||||
if (Channels[0] == null)
|
if (Channels[0] == null)
|
||||||
{
|
{
|
||||||
Logger.Fatal(" Received non-control bunch before control channel was created. ChIndex: {Ch}, ChName: {Name}", bunch.ChIndex, bunch.ChName);
|
Logger.Fatal(" Received non-control bunch before control channel was created. ChIndex: {Ch}, ChName: {Name}", bunch.ChIndex, bunch.ChName);
|
||||||
// TODO: Close();
|
Close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (PlayerController == null && Driver.ClientConnections.Contains(this))
|
else if (PlayerController == null && Driver.ClientConnections.Contains(this))
|
||||||
{
|
{
|
||||||
Logger.Fatal(" Received non-control bunch before player controller was assigned. ChIndex: {Ch}, ChName: {Name}", bunch.ChIndex, bunch.ChName);
|
Logger.Fatal(" Received non-control bunch before player controller was assigned. ChIndex: {Ch}, ChName: {Name}", bunch.ChIndex, bunch.ChName);
|
||||||
// TODO: Close();
|
Close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore control channel close if it hasn't been opened yet
|
// ignore control channel close if it hasn't been opened yet
|
||||||
if (bunch.ChIndex == 0 && Channels[0] == null && bunch.bClose && bunch.ChName == UnrealNames.FNames[UnrealNameKey.Control])
|
if (bunch.ChIndex == 0 && Channels[0] == null && bunch.bClose && bunch.ChName == EName.Control)
|
||||||
{
|
{
|
||||||
Logger.Fatal("Received control channel close before open");
|
Logger.Fatal("Received control channel close before open");
|
||||||
// Close();
|
Close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -827,7 +824,7 @@ public abstract class UNetConnection : UPlayer
|
|||||||
{
|
{
|
||||||
// Unknown type.
|
// Unknown type.
|
||||||
Logger.Fatal("Connection unknown channel type ({Name})", bunch.ChName);
|
Logger.Fatal("Connection unknown channel type ({Name})", bunch.ChName);
|
||||||
// TODO: Close()
|
Close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -883,7 +880,7 @@ public abstract class UNetConnection : UPlayer
|
|||||||
if (Driver.IsServer() && (bunch.IsCriticalError() || bunch.IsError()))
|
if (Driver.IsServer() && (bunch.IsCriticalError() || bunch.IsError()))
|
||||||
{
|
{
|
||||||
Logger.Error("Received corrupted packet data from client {RemoteAddress}. Disconnecting", LowLevelGetRemoteAddress());
|
Logger.Error("Received corrupted packet data from client {RemoteAddress}. Disconnecting", LowLevelGetRemoteAddress());
|
||||||
// TODO: Close()
|
Close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1218,7 +1215,7 @@ public abstract class UNetConnection : UPlayer
|
|||||||
|
|
||||||
if (bIsOpenOrReliable)
|
if (bIsOpenOrReliable)
|
||||||
{
|
{
|
||||||
var name = bunch.ChName;
|
var name = (FName?) bunch.ChName;
|
||||||
UPackageMap.StaticSerializeName(sendBunchHeader, ref name);
|
UPackageMap.StaticSerializeName(sendBunchHeader, ref name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ public abstract class UNetDriver : IAsyncDisposable
|
|||||||
ChannelDefinitionMap = new Dictionary<FName, FChannelDefinition>();
|
ChannelDefinitionMap = new Dictionary<FName, FChannelDefinition>();
|
||||||
ChannelDefinitions = new List<FChannelDefinition>
|
ChannelDefinitions = new List<FChannelDefinition>
|
||||||
{
|
{
|
||||||
new FChannelDefinition(UnrealNames.FNames[UnrealNameKey.Control], typeof(string), 0, true, false, true, false, true),
|
new FChannelDefinition(EName.Control, typeof(string), 0, true, false, true, false, true),
|
||||||
new FChannelDefinition(UnrealNames.FNames[UnrealNameKey.Voice], typeof(string), 1, true, true, true, true, true),
|
new FChannelDefinition(EName.Voice, typeof(string), 1, true, true, true, true, true),
|
||||||
new FChannelDefinition(UnrealNames.FNames[UnrealNameKey.Actor], typeof(string), -1, false, true, false, false, false)
|
new FChannelDefinition(EName.Actor, typeof(string), -1, false, true, false, false, false)
|
||||||
};
|
};
|
||||||
ClientConnections = new List<UNetConnection>();
|
ClientConnections = new List<UNetConnection>();
|
||||||
MappedClientConnections = new ConcurrentDictionary<IPEndPoint, UNetConnection>();
|
MappedClientConnections = new ConcurrentDictionary<IPEndPoint, UNetConnection>();
|
||||||
@@ -179,11 +179,17 @@ public abstract class UNetDriver : IAsyncDisposable
|
|||||||
{
|
{
|
||||||
// TODO: Pool actor channels (?)
|
// TODO: Pool actor channels (?)
|
||||||
|
|
||||||
return (UnrealNameKey)chName.Number switch
|
var name = chName.ToEName();
|
||||||
|
if (name == null)
|
||||||
{
|
{
|
||||||
UnrealNameKey.Actor => new UActorChannel(),
|
throw new UnrealNetException($"Unsupported channel name specified {chName}");
|
||||||
UnrealNameKey.Control => new UControlChannel(),
|
}
|
||||||
UnrealNameKey.Voice => new UVoiceChannel(),
|
|
||||||
|
return name switch
|
||||||
|
{
|
||||||
|
EName.Actor => new UActorChannel(),
|
||||||
|
EName.Control => new UControlChannel(),
|
||||||
|
EName.Voice => new UVoiceChannel(),
|
||||||
_ => throw new UnrealNetException($"Attempted to create unknown channel {chName}")
|
_ => throw new UnrealNetException($"Attempted to create unknown channel {chName}")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Prospect.Unreal.Core;
|
using Prospect.Unreal.Core;
|
||||||
using Prospect.Unreal.Core.Names;
|
using Prospect.Unreal.Core.Names;
|
||||||
|
using Prospect.Unreal.Exceptions;
|
||||||
using Prospect.Unreal.Net.Packets.Bunch;
|
using Prospect.Unreal.Net.Packets.Bunch;
|
||||||
using Prospect.Unreal.Serialization;
|
using Prospect.Unreal.Serialization;
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ namespace Prospect.Unreal.Net;
|
|||||||
|
|
||||||
public class UPackageMap
|
public class UPackageMap
|
||||||
{
|
{
|
||||||
public static unsafe bool StaticSerializeName(FArchive ar, [MaybeNullWhen(false)] ref FName name)
|
public static unsafe bool StaticSerializeName(FArchive ar, ref FName? name)
|
||||||
{
|
{
|
||||||
if (ar.IsLoading())
|
if (ar.IsLoading())
|
||||||
{
|
{
|
||||||
@@ -34,7 +35,7 @@ public class UPackageMap
|
|||||||
if (nameIndex < UnrealNames.MaxHardcodedNameIndex)
|
if (nameIndex < UnrealNames.MaxHardcodedNameIndex)
|
||||||
{
|
{
|
||||||
// hardcoded names never have a Number
|
// hardcoded names never have a Number
|
||||||
name = UnrealNames.FNames[(UnrealNameKey) nameIndex];
|
name = new FName((EName) nameIndex);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -51,17 +52,23 @@ public class UPackageMap
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var bHardcoded = (byte)(ShouldReplicateAsInteger(name) ? 1 : 0);
|
if (name == null)
|
||||||
|
{
|
||||||
|
throw new UnrealException("Name should not be null when saving");
|
||||||
|
}
|
||||||
|
|
||||||
|
var inEName = name.Value.ToEName();
|
||||||
|
var bHardcoded = inEName.HasValue && ShouldReplicateAsInteger(inEName.Value) ? 1 : 0;
|
||||||
ar.SerializeBits(&bHardcoded, 1); // 25
|
ar.SerializeBits(&bHardcoded, 1); // 25
|
||||||
if (bHardcoded != 0)
|
if (bHardcoded != 0)
|
||||||
{
|
{
|
||||||
ar.SerializeIntPacked((uint)name.Number);
|
ar.SerializeIntPacked((uint)inEName!.Value);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// send by string
|
// send by string
|
||||||
var outString = name.Str;
|
var outString = name.Value.GetPlainNameString();
|
||||||
var outNumber = name.Number;
|
var outNumber = name.Value.GetNumber();
|
||||||
|
|
||||||
ar.WriteString(outString);
|
ar.WriteString(outString);
|
||||||
ar.WriteInt32(outNumber);
|
ar.WriteInt32(outNumber);
|
||||||
@@ -71,9 +78,9 @@ public class UPackageMap
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ShouldReplicateAsInteger(FName name)
|
private static bool ShouldReplicateAsInteger(EName name)
|
||||||
{
|
{
|
||||||
return name.Number <= UnrealNames.MaxNetworkedHardcodedName;
|
return (int)name <= UnrealNames.MaxNetworkedHardcodedName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NotifyBunchCommit(int bunchPacketId, FOutBunch bunch)
|
public void NotifyBunchCommit(int bunchPacketId, FOutBunch bunch)
|
||||||
|
|||||||
Reference in New Issue
Block a user