Revert "提交修改"

This reverts commit 5750c4fe56.
This commit is contained in:
2025-10-29 22:41:47 +08:00
parent 5750c4fe56
commit 234b18d3f8
2148 changed files with 5550 additions and 13963 deletions

View File

@@ -0,0 +1,344 @@
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using NBC.Async;
namespace NBC.Helper
{
/// <summary>
/// 提供字节操作辅助方法的静态类。
/// </summary>
public static class ByteHelper
{
private static readonly string[] Suffix = { "Byte", "KB", "MB", "GB", "TB" };
/// <summary>
/// 从指定的文件流中读取一个 64 位整数。
/// </summary>
public static long ReadInt64(FileStream stream)
{
var buffer = new byte[8];
stream.Read(buffer, 0, 8);
return BitConverter.ToInt64(buffer, 0);
}
/// <summary>
/// 从指定的文件流中读取一个 32 位整数。
/// </summary>
public static int ReadInt32(FileStream stream)
{
var buffer = new byte[4];
stream.Read(buffer, 0, 4);
return BitConverter.ToInt32(buffer, 0);
}
/// <summary>
/// 从指定的内存流中读取一个 64 位整数。
/// </summary>
public static long ReadInt64(MemoryStream stream)
{
var buffer = new byte[8];
stream.Read(buffer, 0, 8);
return BitConverter.ToInt64(buffer, 0);
}
/// <summary>
/// 从指定的内存流中读取一个 32 位整数。
/// </summary>
public static int ReadInt32(MemoryStream stream)
{
var buffer = new byte[4];
stream.Read(buffer, 0, 4);
return BitConverter.ToInt32(buffer, 0);
}
/// <summary>
/// 将字节转换为十六进制字符串表示。
/// </summary>
public static string ToHex(this byte b)
{
return b.ToString("X2");
}
/// <summary>
/// 将字节数组转换为十六进制字符串表示。
/// </summary>
public static string ToHex(this byte[] bytes)
{
var stringBuilder = new StringBuilder();
foreach (var b in bytes)
{
stringBuilder.Append(b.ToString("X2"));
}
return stringBuilder.ToString();
}
/// <summary>
/// 将字节数组按指定格式转换为十六进制字符串表示。
/// </summary>
public static string ToHex(this byte[] bytes, string format)
{
var stringBuilder = new StringBuilder();
foreach (var b in bytes)
{
stringBuilder.Append(b.ToString(format));
}
return stringBuilder.ToString();
}
/// <summary>
/// 将字节数组的指定范围按十六进制格式转换为字符串表示。
/// </summary>
public static string ToHex(this byte[] bytes, int offset, int count)
{
var stringBuilder = new StringBuilder();
for (var i = offset; i < offset + count; ++i)
{
stringBuilder.Append(bytes[i].ToString("X2"));
}
return stringBuilder.ToString();
}
/// <summary>
/// 将字节数组转换为默认编码的字符串表示。
/// </summary>
public static string ToStr(this byte[] bytes)
{
return Encoding.Default.GetString(bytes);
}
/// <summary>
/// 将字节数组的指定范围按默认编码转换为字符串表示。
/// </summary>
public static string ToStr(this byte[] bytes, int index, int count)
{
return Encoding.Default.GetString(bytes, index, count);
}
/// <summary>
/// 将字节数组转换为 UTF-8 编码的字符串表示。
/// </summary>
public static string Utf8ToStr(this byte[] bytes)
{
return Encoding.UTF8.GetString(bytes);
}
/// <summary>
/// 将字节数组的指定范围按 UTF-8 编码转换为字符串表示。
/// </summary>
public static string Utf8ToStr(this byte[] bytes, int index, int count)
{
return Encoding.UTF8.GetString(bytes, index, count);
}
/// <summary>
/// 将无符号整数写入字节数组的指定偏移位置。
/// </summary>
public static void WriteTo(this byte[] bytes, int offset, uint num)
{
bytes[offset] = (byte)(num & 0xff);
bytes[offset + 1] = (byte)((num & 0xff00) >> 8);
bytes[offset + 2] = (byte)((num & 0xff0000) >> 16);
bytes[offset + 3] = (byte)((num & 0xff000000) >> 24);
}
/// <summary>
/// 将有符号整数写入字节数组的指定偏移位置。
/// </summary>
public static void WriteTo(this byte[] bytes, int offset, int num)
{
bytes[offset] = (byte)(num & 0xff);
bytes[offset + 1] = (byte)((num & 0xff00) >> 8);
bytes[offset + 2] = (byte)((num & 0xff0000) >> 16);
bytes[offset + 3] = (byte)((num & 0xff000000) >> 24);
}
/// <summary>
/// 将字节写入字节数组的指定偏移位置。
/// </summary>
public static void WriteTo(this byte[] bytes, int offset, byte num)
{
bytes[offset] = num;
}
/// <summary>
/// 将有符号短整数写入字节数组的指定偏移位置。
/// </summary>
public static void WriteTo(this byte[] bytes, int offset, short num)
{
bytes[offset] = (byte)(num & 0xff);
bytes[offset + 1] = (byte)((num & 0xff00) >> 8);
}
/// <summary>
/// 将无符号短整数写入字节数组的指定偏移位置。
/// </summary>
public static void WriteTo(this byte[] bytes, int offset, ushort num)
{
bytes[offset] = (byte)(num & 0xff);
bytes[offset + 1] = (byte)((num & 0xff00) >> 8);
}
/// <summary>
/// 将字节数转换为可读的速度表示。
/// </summary>
/// <param name="byteCount">字节数</param>
/// <returns>可读的速度表示</returns>
public static string ToReadableSpeed(this long byteCount)
{
var i = 0;
double dblSByte = byteCount;
if (byteCount <= 1024)
{
return $"{dblSByte:0.##}{Suffix[i]}";
}
for (i = 0; byteCount / 1024 > 0; i++, byteCount /= 1024)
{
dblSByte = byteCount / 1024.0;
}
return $"{dblSByte:0.##}{Suffix[i]}";
}
/// <summary>
/// 将字节数转换为可读的速度表示。
/// </summary>
/// <param name="byteCount">字节数</param>
/// <returns>可读的速度表示</returns>
public static string ToReadableSpeed(this ulong byteCount)
{
var i = 0;
double dblSByte = byteCount;
if (byteCount <= 1024)
{
return $"{dblSByte:0.##}{Suffix[i]}";
}
for (i = 0; byteCount / 1024 > 0; i++, byteCount /= 1024)
{
dblSByte = byteCount / 1024.0;
}
return $"{dblSByte:0.##}{Suffix[i]}";
}
/// <summary>
/// 合并两个字节数组。
/// </summary>
/// <param name="bytes">第一个字节数组</param>
/// <param name="otherBytes">第二个字节数组</param>
/// <returns>合并后的字节数组</returns>
public static byte[] MergeBytes(byte[] bytes, byte[] otherBytes)
{
var result = new byte[bytes.Length + otherBytes.Length];
bytes.CopyTo(result, 0);
otherBytes.CopyTo(result, bytes.Length);
return result;
}
/// <summary>
/// 根据int值获取字节数组。
/// </summary>
/// <param name="value"></param>
/// <param name="buffer"></param>
/// <exception cref="ArgumentException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void GetBytes(this int value, byte[] buffer)
{
if (buffer.Length < 4)
{
throw new ArgumentException("Buffer too small.");
}
MemoryMarshal.Write(buffer.AsSpan(), ref value);
}
/// <summary>
/// 根据int值获取字节数组。
/// </summary>
/// <param name="memoryStream"></param>
/// <param name="value"></param>
/// <exception cref="ArgumentException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteBytes(this MemoryStream memoryStream, int value)
{
using var memoryOwner = MemoryPool<byte>.Shared.Rent(4);
var memorySpan = memoryOwner.Memory.Span;
MemoryMarshal.Write(memorySpan, ref value);
memoryStream.Write(memorySpan);
}
/// <summary>
/// 根据uint值获取字节数组。
/// </summary>
/// <param name="value"></param>
/// <param name="buffer"></param>
/// <exception cref="ArgumentException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void GetBytes(ref this uint value, byte[] buffer)
{
if (buffer.Length < 4)
{
throw new ArgumentException("Buffer too small.");
}
MemoryMarshal.Write(buffer.AsSpan(), ref value);
}
/// <summary>
/// 根据uint值获取字节数组。
/// </summary>
/// <param name="memoryStream"></param>
/// <param name="value"></param>
/// <exception cref="ArgumentException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteBytes(this MemoryStream memoryStream, uint value)
{
using var memoryOwner = MemoryPool<byte>.Shared.Rent(4);
var memorySpan = memoryOwner.Memory.Span;
MemoryMarshal.Write(memorySpan, ref value);
memoryStream.Write(memorySpan);
}
/// <summary>
/// 根据int值获取字节数组。
/// </summary>
/// <param name="value"></param>
/// <param name="buffer"></param>
/// <exception cref="ArgumentException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void GetBytes(this long value, byte[] buffer)
{
if (buffer.Length < 8)
{
throw new ArgumentException("Buffer too small.");
}
MemoryMarshal.Write(buffer.AsSpan(), ref value);
}
/// <summary>
/// 根据uint值获取字节数组。
/// </summary>
/// <param name="memoryStream"></param>
/// <param name="value"></param>
/// <exception cref="ArgumentException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteBytes(this MemoryStream memoryStream, long value)
{
using var memoryOwner = MemoryPool<byte>.Shared.Rent(8);
var memorySpan = memoryOwner.Memory.Span;
MemoryMarshal.Write(memorySpan, ref value);
memoryStream.Write(memorySpan);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3d992f9fd88d64d9085ab2945d98c3d3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 287852db62f5047f2a0400646628e51d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,48 @@
using System;
using NBC.Async;
using UnityEngine.Networking;
namespace NBC.Unity.Download
{
public abstract class AUnityDownload : IDisposable
{
private long _timeId;
private ulong _totalDownloadedBytes;
private Download _download;
protected UnityWebRequest UnityWebRequest;
private FCancellationToken _cancellationToken;
private Scene Scene;
protected AUnityDownload(Scene scene,Download download)
{
Scene = scene;
_download = download;
_download.Tasks.Add(this);
}
protected UnityWebRequestAsyncOperation Start(UnityWebRequest unityWebRequest, bool monitor)
{
UnityWebRequest = unityWebRequest;
_timeId = Scene.TimerComponent.Unity.RepeatedTimer(33, Update);
return UnityWebRequest.SendWebRequest();
}
private void Update()
{
var downloadSpeed = UnityWebRequest.downloadedBytes - _totalDownloadedBytes;
_download.DownloadSpeed += downloadSpeed;
_download.TotalDownloadedBytes += downloadSpeed;
_totalDownloadedBytes = UnityWebRequest.downloadedBytes;
}
public virtual void Dispose()
{
Update();
_totalDownloadedBytes = 0;
UnityWebRequest?.Dispose();
_download.Tasks.Remove(this);
Scene.TimerComponent.Unity.Remove(ref _timeId);
_download = null;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c5743903d34d474a818b5c2bafa31459
timeCreated: 1726021902

View File

@@ -0,0 +1,70 @@
using System.Collections.Generic;
using System.Linq;
using NBC.Async;
using UnityEngine;
namespace NBC.Unity.Download
{
public sealed class Download
{
public Scene Scene;
public ulong DownloadSpeed;
public ulong TotalDownloadedBytes;
public readonly HashSet<AUnityDownload> Tasks = new();
public static Download Create(Scene scene) => new Download(scene);
private Download(Scene scene)
{
Scene = scene;
}
public void Clear()
{
DownloadSpeed = 0;
TotalDownloadedBytes = 0;
if (Tasks.Count <= 0)
{
return;
}
foreach (var aUnityDownload in Tasks.ToArray())
{
aUnityDownload.Dispose();
}
Tasks.Clear();
}
public FTask<AssetBundle> DownloadAssetBundle(string url, bool monitor = false, FCancellationToken cancellationToken = null)
{
return new DownloadAssetBundle(Scene, this).StartDownload(url, monitor, cancellationToken);
}
public FTask<AudioClip> DownloadAudioClip(string url, AudioType audioType, bool monitor = false, FCancellationToken cancellationToken = null)
{
return new DownloadAudioClip(Scene, this).StartDownload(url, audioType, monitor, cancellationToken);
}
public FTask<Sprite> DownloadSprite(string url, bool monitor = false, FCancellationToken cancellationToken = null)
{
return new DownloadSprite(Scene, this).StartDownload(url, monitor, cancellationToken);
}
public FTask<Texture> DownloadTexture(string url, bool monitor = false, FCancellationToken cancellationToken = null)
{
return new DownloadTexture(Scene, this).StartDownload(url, monitor, cancellationToken);
}
public FTask<string> DownloadText(string url, bool monitor = false, FCancellationToken cancellationToken = null)
{
return new DownloadText(Scene, this).StartDownload(url, monitor, cancellationToken);
}
public FTask<byte[]> DownloadByte(string url, bool monitor = false, FCancellationToken cancellationToken = null)
{
return new DownloadByte(Scene, this).StartDownload(url, monitor, cancellationToken);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5715816370e84842aaab799c9776a5e4
timeCreated: 1726023436

View File

@@ -0,0 +1,52 @@
using System;
using NBC.Async;
using UnityEngine;
using UnityEngine.Networking;
namespace NBC.Unity.Download
{
public sealed class DownloadAssetBundle : AUnityDownload
{
public DownloadAssetBundle(Scene scene, Download download) : base(scene, download)
{
}
public FTask<AssetBundle> StartDownload(string url, bool monitor, FCancellationToken cancellationToken = null)
{
var task = FTask<AssetBundle>.Create(false);
var unityWebRequestAsyncOperation = Start(UnityWebRequestAssetBundle.GetAssetBundle(Uri.EscapeUriString(url)), monitor);
if (cancellationToken != null)
{
cancellationToken.Add(() =>
{
Dispose();
task.SetResult(null);
});
}
unityWebRequestAsyncOperation.completed += operation =>
{
try
{
if (UnityWebRequest.result == UnityWebRequest.Result.Success)
{
var assetBundle = DownloadHandlerAssetBundle.GetContent(UnityWebRequest);
task.SetResult(assetBundle);
}
else
{
Log.Error(UnityWebRequest.error);
task.SetResult(null);
}
}
finally
{
Dispose();
}
};
return task;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 07cbb9a010ed4fe1b90919f81847b9ea
timeCreated: 1726023471

View File

@@ -0,0 +1,53 @@
using System;
using NBC.Async;
using UnityEngine;
using UnityEngine.Networking;
namespace NBC.Unity.Download
{
public sealed class DownloadAudioClip : AUnityDownload
{
public DownloadAudioClip(Scene scene, Download download) : base(scene, download)
{
}
public FTask<AudioClip> StartDownload(string url, AudioType audioType, bool monitor, FCancellationToken cancellationToken = null)
{
var task = FTask<AudioClip>.Create(false);
var unityWebRequestAsyncOperation = Start(UnityWebRequestMultimedia.GetAudioClip(Uri.EscapeUriString(url), audioType), monitor);
if (cancellationToken != null)
{
cancellationToken.Add(() =>
{
Dispose();
task.SetResult(null);
});
}
unityWebRequestAsyncOperation.completed += operation =>
{
try
{
if (UnityWebRequest.result == UnityWebRequest.Result.Success)
{
var audioClip = DownloadHandlerAudioClip.GetContent(UnityWebRequest);
task.SetResult(audioClip);
}
else
{
Log.Error(UnityWebRequest.error);
task.SetResult(null);
}
}
finally
{
Dispose();
}
};
return task;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 66d3739ec33845148534e6ecaf134b73
timeCreated: 1726023476

View File

@@ -0,0 +1,50 @@
using NBC.Async;
using UnityEngine.Networking;
namespace NBC.Unity.Download
{
public sealed class DownloadByte : AUnityDownload
{
public DownloadByte(Scene scene, Download download) : base(scene, download)
{
}
public FTask<byte[]> StartDownload(string url, bool monitor, FCancellationToken cancellationToken = null)
{
var task = FTask<byte[]>.Create(false);
var unityWebRequestAsyncOperation = Start(UnityWebRequest.Get(url), monitor);
if (cancellationToken != null)
{
cancellationToken.Add(() =>
{
Dispose();
task.SetResult(null);
});
}
unityWebRequestAsyncOperation.completed += operation =>
{
try
{
if (UnityWebRequest.result == UnityWebRequest.Result.Success)
{
var bytes = UnityWebRequest.downloadHandler.data;
task.SetResult(bytes);
}
else
{
Log.Error(UnityWebRequest.error);
task.SetResult(null);
}
}
finally
{
Dispose();
}
};
return task;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ae87f3ea9f4e4c9ebabedf45b0bb83b1
timeCreated: 1726023481

View File

@@ -0,0 +1,53 @@
using System;
using NBC.Async;
using UnityEngine;
using UnityEngine.Networking;
namespace NBC.Unity.Download
{
public sealed class DownloadSprite : AUnityDownload
{
public DownloadSprite(Scene scene, Download download) : base(scene, download)
{
}
public FTask<Sprite> StartDownload(string url, bool monitor, FCancellationToken cancellationToken = null)
{
var task = FTask<Sprite>.Create(false);
var unityWebRequestAsyncOperation = Start(UnityWebRequestTexture.GetTexture(Uri.EscapeUriString(url)), monitor);
if (cancellationToken != null)
{
cancellationToken.Add(() =>
{
Dispose();
task.SetResult(null);
});
}
unityWebRequestAsyncOperation.completed += operation =>
{
try
{
if (UnityWebRequest.result == UnityWebRequest.Result.Success)
{
var texture = DownloadHandlerTexture.GetContent(UnityWebRequest);
var sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.one * 5, 1f);
task.SetResult(sprite);
}
else
{
Log.Error(UnityWebRequest.error);
task.SetResult(null);
}
}
finally
{
Dispose();
}
};
return task;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0c2a0f442e974169b7d8b7a5878fe0e6
timeCreated: 1726023487

View File

@@ -0,0 +1,50 @@
using NBC.Async;
using UnityEngine.Networking;
namespace NBC.Unity.Download
{
public sealed class DownloadText : AUnityDownload
{
public DownloadText(Scene scene, Download download) : base(scene, download)
{
}
public FTask<string> StartDownload(string url, bool monitor, FCancellationToken cancellationToken = null)
{
var task = FTask<string>.Create(false);
var unityWebRequestAsyncOperation = Start(UnityWebRequest.Get(url), monitor);
if (cancellationToken != null)
{
cancellationToken.Add(() =>
{
Dispose();
task.SetResult(null);
});
}
unityWebRequestAsyncOperation.completed += operation =>
{
try
{
if (UnityWebRequest.result == UnityWebRequest.Result.Success)
{
var text = UnityWebRequest.downloadHandler.text;
task.SetResult(text);
}
else
{
Log.Error(UnityWebRequest.error);
task.SetResult(null);
}
}
finally
{
Dispose();
}
};
return task;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4284aafa8572453cb75920d2d58e9c50
timeCreated: 1726023491

View File

@@ -0,0 +1,52 @@
using System;
using NBC.Async;
using UnityEngine;
using UnityEngine.Networking;
namespace NBC.Unity.Download
{
public sealed class DownloadTexture : AUnityDownload
{
public DownloadTexture(Scene scene, Download download) : base(scene, download)
{
}
public FTask<Texture> StartDownload(string url, bool monitor, FCancellationToken cancellationToken = null)
{
var task = FTask<Texture>.Create(false);
var unityWebRequestAsyncOperation = Start(UnityWebRequestTexture.GetTexture(Uri.EscapeUriString(url)), monitor);
if (cancellationToken != null)
{
cancellationToken.Add(() =>
{
Dispose();
task.SetResult(null);
});
}
unityWebRequestAsyncOperation.completed += operation =>
{
try
{
if (UnityWebRequest.result == UnityWebRequest.Result.Success)
{
var texture = DownloadHandlerTexture.GetContent(UnityWebRequest);
task.SetResult(texture);
}
else
{
Log.Error(UnityWebRequest.error);
task.SetResult(null);
}
}
finally
{
Dispose();
}
};
return task;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5eaa6023378844ebb51e4b80425d8a4e
timeCreated: 1726023496

View File

@@ -0,0 +1,59 @@
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace NBC.Helper
{
/// <summary>
/// 提供计算 MD5 散列值的辅助方法。
/// </summary>
public static partial class EncryptHelper
{
private static readonly SHA256 Sha256Hash = SHA256.Create();
/// <summary>
/// 计算指定字节数组的Sha256。
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static byte[] ComputeSha256Hash(byte[] bytes)
{
using var sha256Hash = SHA256.Create();
return sha256Hash.ComputeHash(bytes);
}
/// <summary>
/// 计算指定文件的 MD5 散列值。
/// </summary>
/// <param name="filePath">要计算散列值的文件路径。</param>
/// <returns>表示文件的 MD5 散列值的字符串。</returns>
public static string FileMD5(string filePath)
{
using var file = new FileStream(filePath, FileMode.Open);
return FileMD5(file);
}
/// <summary>
/// 计算给定文件流的 MD5 散列值。
/// </summary>
/// <param name="fileStream">要计算散列值的文件流。</param>
/// <returns>表示文件流的 MD5 散列值的字符串。</returns>
public static string FileMD5(FileStream fileStream)
{
var md5 = MD5.Create();
return md5.ComputeHash(fileStream).ToHex("x2");
}
/// <summary>
/// 计算给定字节数组的 MD5 散列值。
/// </summary>
/// <param name="bytes">要计算散列值的字节数组。</param>
/// <returns>表示字节数组的 MD5 散列值的字符串。</returns>
public static string BytesMD5(byte[] bytes)
{
var md5 = MD5.Create();
bytes = md5.ComputeHash(bytes);
return bytes.ToHex("x2");
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2c09f5dc7247c4396b293e9c91b42361
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,186 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace NBC.Helper
{
/// <summary>
/// 文件操作助手类,提供了各种文件操作方法。
/// </summary>
public static partial class FileHelper
{
/// <summary>
/// 获取相对路径的完整路径。
/// </summary>
/// <param name="relativePath">相对路径。</param>
/// <returns>完整路径。</returns>
public static string GetFullPath(string relativePath)
{
return Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), relativePath));
}
/// <summary>
/// 获取相对路径的完整路径。
/// </summary>
/// <param name="relativePath">相对于指定的目录的相对路径。</param>
/// <param name="srcDir">指定的目录</param>
/// <returns>完整路径。</returns>
public static string GetFullPath(string relativePath, string srcDir)
{
return Path.GetFullPath(Path.Combine(srcDir, relativePath));
}
/// <summary>
/// 获取相对路径的的文本信息。
/// </summary>
/// <param name="relativePath"></param>
/// <returns></returns>
public static async Task<string> GetTextByRelativePath(string relativePath)
{
var fullPath = GetFullPath(relativePath);
return await File.ReadAllTextAsync(fullPath, Encoding.UTF8);
}
/// <summary>
/// 获取绝对路径的的文本信息。
/// </summary>
/// <param name="fullPath"></param>
/// <returns></returns>
public static async Task<string> GetText(string fullPath)
{
return await File.ReadAllTextAsync(fullPath, Encoding.UTF8);
}
/// <summary>
/// 根据文件夹路径创建文件夹,如果文件夹不存在会自动创建文件夹。
/// </summary>
/// <param name="directoryPath"></param>
public static void CreateDirectory(string directoryPath)
{
if (directoryPath.LastIndexOf('/') != directoryPath.Length - 1)
{
directoryPath += "/";
}
var directoriesByFilePath = GetDirectoriesByFilePath(directoryPath);
foreach (var dir in directoriesByFilePath)
{
if (Directory.Exists(dir))
{
continue;
}
Directory.CreateDirectory(dir);
}
}
/// <summary>
/// 将文件复制到目标路径,如果目标目录不存在会自动创建目录。
/// </summary>
/// <param name="sourceFile">源文件路径。</param>
/// <param name="destinationFile">目标文件路径。</param>
/// <param name="overwrite">是否覆盖已存在的目标文件。</param>
public static void Copy(string sourceFile, string destinationFile, bool overwrite)
{
CreateDirectory(destinationFile);
File.Copy(sourceFile, destinationFile, overwrite);
}
/// <summary>
/// 获取文件路径内的所有文件夹路径。
/// </summary>
/// <param name="filePath">文件路径。</param>
/// <returns>文件夹路径列表。</returns>
public static IEnumerable<string> GetDirectoriesByFilePath(string filePath)
{
var dir = "";
var fileDirectories = filePath.Split('/');
for (var i = 0; i < fileDirectories.Length - 1; i++)
{
dir = $"{dir}{fileDirectories[i]}/";
yield return dir;
}
if (fileDirectories.Length == 1)
{
yield return filePath;
}
}
/// <summary>
/// 将文件夹内的所有内容复制到目标位置。
/// </summary>
/// <param name="sourceDirectory">源文件夹路径。</param>
/// <param name="destinationDirectory">目标文件夹路径。</param>
/// <param name="overwrite">是否覆盖已存在的文件。</param>
public static void CopyDirectory(string sourceDirectory, string destinationDirectory, bool overwrite)
{
// 创建目标文件夹
if (!Directory.Exists(destinationDirectory))
{
Directory.CreateDirectory(destinationDirectory);
}
// 获取当前文件夹中的所有文件
var files = Directory.GetFiles(sourceDirectory);
// 拷贝文件到目标文件夹
foreach (var file in files)
{
var fileName = Path.GetFileName(file);
var destinationPath = Path.Combine(destinationDirectory, fileName);
File.Copy(file, destinationPath, overwrite);
}
// 获取源文件夹中的所有子文件夹
var directories = Directory.GetDirectories(sourceDirectory);
// 递归方式拷贝文件夹
foreach (var directory in directories)
{
var directoryName = Path.GetFileName(directory);
var destinationPath = Path.Combine(destinationDirectory, directoryName);
CopyDirectory(directory, destinationPath, overwrite);
}
}
/// <summary>
/// 获取目录下的所有文件
/// </summary>
/// <param name="folderPath">文件夹路径。</param>
/// <param name="searchPattern">需要查找的文件通配符</param>
/// <param name="searchOption">查找的类型</param>
/// <returns></returns>
public static string[] GetDirectoryFile(string folderPath, string searchPattern, SearchOption searchOption)
{
return Directory.GetFiles(folderPath, searchPattern, searchOption);
}
/// <summary>
/// 清空文件夹内的所有文件。
/// </summary>
/// <param name="folderPath">文件夹路径。</param>
public static void ClearDirectoryFile(string folderPath)
{
if (!Directory.Exists(folderPath))
{
return;
}
var files = Directory.GetFiles(folderPath);
foreach (var file in files)
{
File.Delete(file);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cb7aa7f0698f4409da7f0cdb32e8d8ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,143 @@
using System.Security.Cryptography;
using System.Text;
// ReSharper disable InconsistentNaming
namespace NBC.Helper
{
/// <summary>
/// HashCode算法帮助类
/// </summary>
public static partial class HashCodeHelper
{
private static readonly SHA256 Sha256Hash = SHA256.Create();
/// <summary>
/// 计算两个字符串的HashCode
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static int GetHashCode(string a, string b)
{
var hash = 17;
hash = hash * 31 + a.GetHashCode();
hash = hash * 31 + b.GetHashCode();
return hash;
}
/// <summary>
/// 使用bkdr算法生成一个long的值
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static unsafe long GetBKDRHashCode(string str)
{
ulong hash = 0;
// 如果要修改这个种子、建议选择一个质数来做种子
const uint seed = 13131; // 31 131 1313 13131 131313 etc..
fixed (char* p = str)
{
for (var i = 0; i < str.Length; i++)
{
var c = p[i];
var high = (byte)(c >> 8);
var low = (byte)(c & byte.MaxValue);
hash = hash * seed + high;
hash = hash * seed + low;
}
}
return (long)hash;
}
/// <summary>
/// 使用MurmurHash3算法生成一个uint的值
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static unsafe uint MurmurHash3(string str)
{
const uint seed = 0xc58f1a7b;
uint hash = seed;
uint c1 = 0xcc9e2d51;
uint c2 = 0x1b873593;
fixed (char* p = str)
{
var current = p;
for (var i = 0; i < str.Length; i++)
{
var k1 = (uint)(*current);
k1 *= c1;
k1 = (k1 << 15) | (k1 >> (32 - 15));
k1 *= c2;
hash ^= k1;
hash = (hash << 13) | (hash >> (32 - 13));
hash = hash * 5 + 0xe6546b64;
current++;
}
}
hash ^= (uint)str.Length;
hash ^= hash >> 16;
hash *= 0x85ebca6b;
hash ^= hash >> 13;
hash *= 0xc2b2ae35;
hash ^= hash >> 16;
return hash;
}
/// <summary>
/// 使用MurmurHash3算法生成一个long的值
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static unsafe long ComputeHash64(string str)
{
const ulong seed = 0xc58f1a7bc58f1a7bUL; // 64-bit seed
var hash = seed;
var c1 = 0x87c37b91114253d5UL;
var c2 = 0x4cf5ad432745937fUL;
fixed (char* p = str)
{
var current = p;
for (var i = 0; i < str.Length; i++)
{
var k1 = (ulong)(*current);
k1 *= c1;
k1 = (k1 << 31) | (k1 >> (64 - 31));
k1 *= c2;
hash ^= k1;
hash = (hash << 27) | (hash >> (64 - 27));
hash = hash * 5 + 0x52dce729;
current++;
}
}
hash ^= (ulong)str.Length;
hash ^= hash >> 33;
hash *= 0xff51afd7ed558ccdUL;
hash ^= hash >> 33;
hash *= 0xc4ceb9fe1a85ec53UL;
hash ^= hash >> 33;
return (long)hash;
}
/// <summary>
/// 根据字符串计算一个Hash值
/// </summary>
/// <param name="rawData"></param>
/// <returns></returns>
public static int ComputeSha256HashAsInt(string rawData)
{
var bytes = Sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(rawData));
return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6ec2f7d0b1e3d4605bc7224099e156a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 724626f87e31e4ac4a3098336100c034
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,145 @@
#if !FANTASY_WEBGL
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using NBC.Helper;
using NBC.Async;
using NBC.Pool;
#pragma warning disable CS8603 // Possible null reference return.
namespace NBC.Http
{
/// <summary>
/// HTTP帮助类
/// </summary>
public static partial class HttpClientHelper
{
private static readonly HttpClient Client = new HttpClient(new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
});
/// <summary>
/// 用Post方式请求string数据
/// </summary>
/// <param name="url"></param>
/// <param name="content"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static async FTask<string> CallNotDeserializeByPost(string url, HttpContent content)
{
var response = await Client.PostAsync(url, content);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"Unable to connect to server url {(object)url} HttpStatusCode:{(object)response.StatusCode}");
}
return await response.Content.ReadAsStringAsync();
}
/// <summary>
/// 用Get方式请求string数据
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static async FTask<string> CallNotDeserializeByGet(string url)
{
var response = await Client.GetAsync(url);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"Unable to connect to server url {(object)url} HttpStatusCode:{(object)response.StatusCode}");
}
return await response.Content.ReadAsStringAsync();
}
/// <summary>
/// 用Post方式请求JSON数据并自动把JSON转换为对象。
/// </summary>
/// <param name="url"></param>
/// <param name="content"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static async FTask<T> CallByPost<T>(string url, HttpContent content)
{
return await Deserialize<T>(url, await Client.PostAsync(url, content));
}
/// <summary>
/// 用Post方式请求JSON数据并自动把JSON转换为对象。
/// </summary>
/// <param name="url"></param>
/// <param name="method"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static async FTask<T> CallByPost<T>(string url, HttpMethod method)
{
return await Deserialize<T>(url, await Client.SendAsync(new HttpRequestMessage(method, url)));
}
/// <summary>
/// 用Get方式请求JSON数据并自动把JSON转换为对象。
/// </summary>
/// <param name="url"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static async FTask<T> CallByGet<T>(string url)
{
return await Deserialize<T>(url, await Client.GetAsync(url));
}
/// <summary>
/// 用Post方式请求JSON数据并自动把JSON转换为对象。
/// </summary>
/// <param name="url"></param>
/// <param name="id"></param>
/// <param name="authentication"></param>
/// <param name="method"></param>
/// <param name="params"></param>
/// <typeparam name="TRequest"></typeparam>
/// <typeparam name="TResponse"></typeparam>
/// <returns></returns>
public static async FTask<TResponse> Call<TRequest, TResponse>(string url, int id, AuthenticationHeaderValue authentication, string method, params object[] @params) where TRequest : class, IJsonRpcRequest, new()
{
var request = Pool<TRequest>.Rent();
using var httpClientPool = HttpClientPool.Create();
var client = httpClientPool.Client;
client.DefaultRequestHeaders.Authorization = authentication;
try
{
request.Init(method, id, @params);
var content = new StringContent(request.ToJson(), Encoding.UTF8, "application/json");
var response = await Deserialize<TResponse>(url, await client.PostAsync(url, content));
return response;
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Pool<TRequest>.Return(request);
}
return default;
}
private static async FTask<T> Deserialize<T>(string url, HttpResponseMessage response)
{
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"Unable to connect to server url {(object)url} HttpStatusCode:{(object)response.StatusCode}");
}
return (await response.Content.ReadAsStringAsync()).Deserialize<T>();
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f8005f3a1a1945a2929442f82832e765
timeCreated: 1726023741

View File

@@ -0,0 +1,44 @@
#if !FANTASY_WEBGL
using System;
using System.Collections.Generic;
using System.Net.Http;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
namespace NBC.Http
{
internal class HttpClientPool : IDisposable
{
private bool IsDispose { get; set; }
public HttpClient Client { get; private set; }
private static readonly Queue<HttpClientPool> Pools = new Queue<HttpClientPool>();
private static readonly HttpClientHandler ClientHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
};
public static HttpClientPool Create()
{
if (Pools.TryDequeue(out var httpClientPool))
{
httpClientPool.IsDispose = false;
return httpClientPool;
}
httpClientPool = new HttpClientPool();
httpClientPool.Client = new HttpClient(ClientHandler);
return httpClientPool;
}
public void Dispose()
{
if (IsDispose)
{
return;
}
IsDispose = true;
Pools.Enqueue(this);
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a78c441357d244d5ba490a13c89e1c50
timeCreated: 1726023895

View File

@@ -0,0 +1,20 @@
using NBC.Pool;
#if !FANTASY_WEBGL
namespace NBC.Http
{
/// <summary>
/// 一个JsonRPC的接口
/// </summary>
public interface IJsonRpcRequest : IPool
{
/// <summary>
/// 用于初始化这个Json对象
/// </summary>
/// <param name="method"></param>
/// <param name="id"></param>
/// <param name="params"></param>
void Init(string method, int id, params object[] @params);
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 72a03580c619417b9f8f92d99938e371
timeCreated: 1726023900

View File

@@ -0,0 +1,57 @@
using System;
using Newtonsoft.Json;
#pragma warning disable CS8603
namespace NBC.Helper
{
/// <summary>
/// 提供操作 JSON 数据的辅助方法。
/// </summary>
public static partial class JsonHelper
{
/// <summary>
/// 将对象序列化为 JSON 字符串。
/// </summary>
/// <typeparam name="T">要序列化的对象类型。</typeparam>
/// <param name="t">要序列化的对象。</param>
/// <returns>表示序列化对象的 JSON 字符串。</returns>
public static string ToJson<T>(this T t)
{
return JsonConvert.SerializeObject(t);
}
/// <summary>
/// 反序列化 JSON 字符串为指定类型的对象。
/// </summary>
/// <param name="json">要反序列化的 JSON 字符串。</param>
/// <param name="type">目标对象的类型。</param>
/// <param name="reflection">是否使用反射进行反序列化(默认为 true。</param>
/// <returns>反序列化后的对象。</returns>
public static object Deserialize(this string json, Type type, bool reflection = true)
{
return JsonConvert.DeserializeObject(json, type);
}
/// <summary>
/// 反序列化 JSON 字符串为指定类型的对象。
/// </summary>
/// <typeparam name="T">目标对象的类型。</typeparam>
/// <param name="json">要反序列化的 JSON 字符串。</param>
/// <returns>反序列化后的对象。</returns>
public static T Deserialize<T>(this string json)
{
return JsonConvert.DeserializeObject<T>(json);
}
/// <summary>
/// 克隆对象,通过将对象序列化为 JSON然后再进行反序列化。
/// </summary>
/// <typeparam name="T">要克隆的对象类型。</typeparam>
/// <param name="t">要克隆的对象。</param>
/// <returns>克隆后的对象。</returns>
public static T Clone<T>(T t)
{
return t.ToJson().Deserialize<T>();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5f25483965eb6459583e1d328adc8f05
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,443 @@
#if !FANTASY_WEBGL
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#pragma warning disable CS8603 // Possible null reference return.
// ReSharper disable InconsistentNaming
namespace NBC.Helper
{
/// <summary>
/// 提供网络操作相关的帮助方法。
/// </summary>
public static partial class NetworkHelper
{
/// <summary>
/// 根据字符串获取一个IPEndPoint
/// </summary>
/// <param name="address"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IPEndPoint GetIPEndPoint(string address)
{
try
{
var addressSplit = address.Split(':');
if (addressSplit.Length != 2)
{
throw new FormatException("Invalid format");
}
var ipString = addressSplit[0];
var portString = addressSplit[1];
if (!IPAddress.TryParse(ipString, out var ipAddress))
{
throw new FormatException("Invalid IP address");
}
if (!int.TryParse(portString, out var port) || port < 0 || port > 65535)
{
throw new FormatException("Invalid port number");
}
return new IPEndPoint(ipAddress, port);
}
catch (Exception e)
{
Log.Error($"Error parsing IP and Port:{e.Message}");
return null;
}
}
/// <summary>
/// 克隆一个IPEndPoint
/// </summary>
/// <param name="endPoint"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IPEndPoint Clone(this EndPoint endPoint)
{
var ip = (IPEndPoint)endPoint;
return new IPEndPoint(ip.Address, ip.Port);
}
/// <summary>
/// 比较两个IPEndPoint是否相等
/// </summary>
/// <param name="endPoint"></param>
/// <param name="ipEndPoint"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IPEndPointEquals(this EndPoint endPoint, IPEndPoint ipEndPoint)
{
var ip = (IPEndPoint)endPoint;
return ip.Address.Equals(ipEndPoint.Address) && ip.Port == ipEndPoint.Port;
}
/// <summary>
/// 比较两个IPEndPoint是否相等
/// </summary>
/// <param name="endPoint"></param>
/// <param name="ipEndPoint"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IPEndPointEquals(this IPEndPoint endPoint, IPEndPoint ipEndPoint)
{
return endPoint.Address.Equals(ipEndPoint.Address) && endPoint.Port == ipEndPoint.Port;
}
#if !FANTASY_WEBGL
/// <summary>
/// 将SocketAddress写入到Byte[]中
/// </summary>
/// <param name="socketAddress"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void SocketAddressToByte(this SocketAddress socketAddress, byte[] buffer, int offset)
{
if (socketAddress == null)
{
throw new ArgumentNullException(nameof(socketAddress), "The SocketAddress cannot be null.");
}
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer), "The buffer cannot be null.");
}
if (buffer.Length < socketAddress.Size + offset + 8)
{
throw new ArgumentException("The buffer length is insufficient. It must be at least the size of the SocketAddress plus 8 bytes.", nameof(buffer));
}
fixed (byte* pBuffer = buffer)
{
var pOffsetBuffer = pBuffer + offset;
var addressFamilyValue = (int)socketAddress.Family;
var socketAddressSizeValue = socketAddress.Size;
Buffer.MemoryCopy(&addressFamilyValue, pOffsetBuffer, buffer.Length - offset, sizeof(int));
Buffer.MemoryCopy(&socketAddressSizeValue, pOffsetBuffer + 4, buffer.Length - offset -4, sizeof(int));
for (var i = 0; i < socketAddress.Size - 2; i++)
{
pOffsetBuffer[8 + i] = socketAddress[i + 2];
}
}
}
/// <summary>
/// 将byre[]转换为SocketAddress
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="socketAddress"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="InvalidOperationException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int ByteToSocketAddress(byte[] buffer, int offset, out SocketAddress socketAddress)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer), "The buffer cannot be null.");
}
if (buffer.Length < 8)
{
throw new ArgumentException("Buffer length is insufficient. It must be at least 8 bytes.", nameof(buffer));
}
try
{
fixed (byte* pBuffer = buffer)
{
var pOffsetBuffer = pBuffer + offset;
var addressFamily = (AddressFamily)Marshal.ReadInt32((IntPtr)pOffsetBuffer);
var socketAddressSize = Marshal.ReadInt32((IntPtr)(pOffsetBuffer + 4));
if (buffer.Length < offset + 8 + socketAddressSize)
{
throw new ArgumentException("Buffer length is insufficient for the given SocketAddress size.", nameof(buffer));
}
socketAddress = new SocketAddress(addressFamily, socketAddressSize);
for (var i = 0; i < socketAddressSize - 2; i++)
{
socketAddress[i + 2] = *(pOffsetBuffer + 8 + i);
}
return 8 + offset + socketAddressSize;
}
}
catch (ArgumentNullException ex)
{
throw new InvalidOperationException("An argument provided to the method is null.", ex);
}
catch (ArgumentException ex)
{
throw new InvalidOperationException("An argument provided to the method is invalid.", ex);
}
catch (Exception ex)
{
throw new InvalidOperationException("An unexpected error occurred while processing the buffer.", ex);
}
}
/// <summary>
/// 将ReadOnlyMemory转换为SocketAddress
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="socketAddress"></param>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="InvalidOperationException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int ByteToSocketAddress(ReadOnlyMemory<byte> buffer, int offset, out SocketAddress socketAddress)
{
if (buffer.Length < 8)
{
throw new ArgumentException("Buffer length is insufficient. It must be at least 8 bytes.", nameof(buffer));
}
try
{
fixed (byte* pBuffer = buffer.Span)
{
var pOffsetBuffer = pBuffer + offset;
var addressFamily = (AddressFamily)Marshal.ReadInt32((IntPtr)pOffsetBuffer);
var socketAddressSize = Marshal.ReadInt32((IntPtr)(pOffsetBuffer + 4));
if (buffer.Length < offset + 8 + socketAddressSize)
{
throw new ArgumentException("Buffer length is insufficient for the given SocketAddress size.", nameof(buffer));
}
socketAddress = new SocketAddress(addressFamily, socketAddressSize);
for (var i = 0; i < socketAddressSize - 2; i++)
{
socketAddress[i + 2] = *(pOffsetBuffer + 8 + i);
}
return 8 + offset + socketAddressSize;
}
}
catch (ArgumentNullException ex)
{
throw new InvalidOperationException("An argument provided to the method is null.", ex);
}
catch (ArgumentException ex)
{
throw new InvalidOperationException("An argument provided to the method is invalid.", ex);
}
catch (Exception ex)
{
throw new InvalidOperationException("An unexpected error occurred while processing the buffer.", ex);
}
}
/// <summary>
/// 根据SocketAddress获得IPEndPoint
/// </summary>
/// <param name="socketAddress"></param>
/// <returns></returns>
/// <exception cref="NotSupportedException"></exception>
public static unsafe IPEndPoint GetIPEndPoint(this SocketAddress socketAddress)
{
switch (socketAddress.Family)
{
case AddressFamily.InterNetwork:
{
var ipBytes = new byte[4];
for (var i = 0; i < 4; i++)
{
ipBytes[i] = socketAddress[4 + i];
}
var port = (socketAddress[2] << 8) + socketAddress[3];
var ip = new IPAddress(ipBytes);
return new IPEndPoint(ip, port);
}
case AddressFamily.InterNetworkV6:
{
var ipBytes = new byte[16];
Span<byte> socketAddressSpan = stackalloc byte[28];
for (var i = 0; i < 28; i++)
{
socketAddressSpan[i] = socketAddress[i];
}
fixed (byte* pSocketAddress = socketAddressSpan)
{
for (var i = 0; i < 16; i++)
{
ipBytes[i] = *(pSocketAddress + 8 + i);
}
var port = (*(pSocketAddress + 2) << 8) + *(pSocketAddress + 3);
var scopeId = Marshal.ReadInt64((IntPtr)(pSocketAddress + 24));
var ip = new IPAddress(ipBytes, scopeId);
return new IPEndPoint(ip, port);
}
}
default:
{
throw new NotSupportedException("Address family not supported.");
}
}
}
#endif
/// <summary>
/// 获取本机所有网络适配器的IP地址。
/// </summary>
/// <returns>IP地址数组。</returns>
public static string[] GetAddressIPs()
{
var list = new List<string>();
foreach (var networkInterface in NetworkInterface.GetAllNetworkInterfaces())
{
// 仅考虑以太网类型的网络适配器
if (networkInterface.NetworkInterfaceType != NetworkInterfaceType.Ethernet)
{
continue;
}
foreach (var add in networkInterface.GetIPProperties().UnicastAddresses)
{
list.Add(add.Address.ToString());
}
}
return list.ToArray();
}
/// <summary>
/// 将主机名和端口号转换为 <see cref="IPEndPoint"/> 实例。
/// </summary>
/// <param name="host">主机名。</param>
/// <param name="port">端口号。</param>
/// <returns><see cref="IPEndPoint"/> 实例。</returns>
public static IPEndPoint ToIPEndPoint(string host, int port)
{
return new IPEndPoint(IPAddress.Parse(host), port);
}
/// <summary>
/// 将地址字符串转换为 <see cref="IPEndPoint"/> 实例。
/// </summary>
/// <param name="address">地址字符串,格式为 "主机名:端口号"。</param>
/// <returns><see cref="IPEndPoint"/> 实例。</returns>
public static IPEndPoint ToIPEndPoint(string address)
{
var index = address.LastIndexOf(':');
var host = address.Substring(0, index);
var p = address.Substring(index + 1);
var port = int.Parse(p);
return ToIPEndPoint(host, port);
}
/// <summary>
/// 将 <see cref="IPEndPoint"/> 实例转换为字符串表示形式。
/// </summary>
/// <param name="self"><see cref="IPEndPoint"/> 实例。</param>
/// <returns>表示 <see cref="IPEndPoint"/> 的字符串。</returns>
public static string IPEndPointToStr(this IPEndPoint self)
{
return $"{self.Address}:{self.Port}";
}
/// <summary>
/// 针对 Windows 平台设置UDP连接重置选项。
/// </summary>
/// <param name="socket">要设置选项的 <see cref="Socket"/> 实例。</param>
public static void SetSioUdpConnReset(this Socket socket)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;
}
/*
目前这个问题只有Windows下才会出现。
服务器端在发送数据时捕获到了一个异常,
这个异常导致原因应该是远程客户端的UDP监听已停止导致数据发送出错。
按理说UDP是无连接的报这个异常是不合理的
这个异常让整UDP的服务监听也停止了。
这样就因为一个客户端的数据发送无法到达而导致了服务挂了,所有客户端都无法与服务器通信了
想详细了解看下https://blog.csdn.net/sunzhen6251/article/details/124168805*/
const uint IOC_IN = 0x80000000;
const uint IOC_VENDOR = 0x18000000;
const int SIO_UDP_CONNRESET = unchecked((int) (IOC_IN | IOC_VENDOR | 12));
socket.IOControl(SIO_UDP_CONNRESET, new[] {Convert.ToByte(false)}, null);
}
/// <summary>
/// 将 Socket 缓冲区大小设置为操作系统限制。
/// </summary>
/// <param name="socket">要设置缓冲区大小的 Socket。</param>
public static void SetSocketBufferToOsLimit(this Socket socket)
{
socket.SetReceiveBufferToOSLimit();
socket.SetSendBufferToOSLimit();
}
/// <summary>
/// 将 Socket 接收缓冲区大小设置为操作系统限制。
/// 尝试增加接收缓冲区大小的次数 = 默认 + 最大增加 100 MB。
/// </summary>
/// <param name="socket">要设置接收缓冲区大小的 Socket。</param>
/// <param name="stepSize">每次增加的步长大小。</param>
/// <param name="attempts">尝试增加缓冲区大小的次数。</param>
public static void SetReceiveBufferToOSLimit(this Socket socket, int stepSize = 1024, int attempts = 100_000)
{
// setting a too large size throws a socket exception.
// so let's keep increasing until we encounter it.
for (int i = 0; i < attempts; ++i)
{
// increase in 1 KB steps
try
{
socket.ReceiveBufferSize += stepSize;
}
catch (SocketException)
{
break;
}
}
}
/// <summary>
/// 将 Socket 发送缓冲区大小设置为操作系统限制。
/// 尝试增加发送缓冲区大小的次数 = 默认 + 最大增加 100 MB。
/// </summary>
/// <param name="socket">要设置发送缓冲区大小的 Socket。</param>
/// <param name="stepSize">每次增加的步长大小。</param>
/// <param name="attempts">尝试增加缓冲区大小的次数。</param>
public static void SetSendBufferToOSLimit(this Socket socket, int stepSize = 1024, int attempts = 100_000)
{
// setting a too large size throws a socket exception.
// so let's keep increasing until we encounter it.
for (var i = 0; i < attempts; ++i)
{
// increase in 1 KB steps
try
{
socket.SendBufferSize += stepSize;
}
catch (SocketException)
{
break;
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f35b5f3e69dae431982e69500c1c97c6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,310 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using NBC.LowLevel;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
namespace NBC.Helper
{
/// <summary>
/// 随机数操作助手类,提供各种随机数生成和操作方法。
/// </summary>
public static partial class RandomHelper
{
[ThreadStatic]
private static Random _random;
/// <summary>
/// 生成一个随机的无符号 64 位整数。
/// </summary>
/// <returns>无符号 64 位整数。</returns>
public static ulong RandUInt64()
{
var byte8 = new FixedBytes8().AsSpan();
var random = _random ??= new Random();
random.NextBytes(byte8);
return BitConverter.ToUInt64(byte8);
}
/// <summary>
/// 生成一个随机的 64 位整数。
/// </summary>
/// <returns>64 位整数。</returns>
public static long RandInt64()
{
var byte8 = new FixedBytes8().AsSpan();
var random = _random ??= new Random();
random.NextBytes(byte8);
return BitConverter.ToInt64(byte8);
}
/// <summary>
/// 生成一个随机的无符号 32 位整数。
/// </summary>
/// <returns>无符号 32 位整数。</returns>
public static uint RandUInt32()
{
var random = _random ??= new Random();
return (uint) random.Next();
}
/// <summary>
/// 生成一个随机的无符号 16 位整数。
/// </summary>
/// <returns>无符号 16 位整数。</returns>
public static ushort RandUInt16()
{
var byte2 = new FixedBytes2().AsSpan();
var random = _random ??= new Random();
random.NextBytes(byte2);
return BitConverter.ToUInt16(byte2);
}
/// <summary>
/// 在指定范围内生成一个随机整数(包含下限,不包含上限)。
/// </summary>
/// <param name="lower">下限。</param>
/// <param name="upper">上限。</param>
/// <returns>生成的随机整数。</returns>
public static int RandomNumber(int lower, int upper)
{
var random = _random ??= new Random();
return random.Next(lower, upper);
}
/// <summary>
/// 生成一个随机的布尔值。
/// </summary>
/// <returns>随机的布尔值。</returns>
public static bool RandomBool()
{
var random = _random ??= new Random();
return (random.Next() & 1) == 0;
}
/// <summary>
/// 从数组中随机选择一个元素。
/// </summary>
/// <typeparam name="T">数组元素的类型。</typeparam>
/// <param name="array">要选择的数组。</param>
/// <returns>随机选择的数组元素。</returns>
public static T RandomArray<T>(this T[] array)
{
return array[RandomNumber(0, array.Count())];
}
/// <summary>
/// 从列表中随机选择一个元素。
/// </summary>
/// <typeparam name="T">列表元素的类型。</typeparam>
/// <param name="array">要选择的列表。</param>
/// <returns>随机选择的列表元素。</returns>
public static T RandomArray<T>(this List<T> array)
{
return array[RandomNumber(0, array.Count())];
}
/// <summary>
/// 打乱列表中元素的顺序。
/// </summary>
/// <typeparam name="T">列表元素的类型。</typeparam>
/// <param name="arr">要打乱顺序的列表。</param>
public static void BreakRank<T>(List<T> arr)
{
if (arr == null || arr.Count < 2)
{
return;
}
var random = _random ??= new Random();
for (var i = 0; i < arr.Count / 2; i++)
{
var index = random.Next(0, arr.Count);
(arr[index], arr[arr.Count - index - 1]) = (arr[arr.Count - index - 1], arr[index]);
}
}
/// <summary>
/// 生成一个介于 0 和 1 之间的随机单精度浮点数。
/// </summary>
/// <returns>随机单精度浮点数。</returns>
public static float RandFloat01()
{
var random = _random ??= new Random();
var value = random.NextDouble();
return (float) value;
}
private static int Rand(int n)
{
var rd = new Random();
// 注意返回值是左闭右开所以maxValue要加1
return rd.Next(1, n + 1);
}
/// <summary>
/// 根据权重随机选择一个索引。
/// </summary>
/// <param name="weights">权重数组,每个元素表示相应索引的权重。</param>
/// <returns>随机选择的索引值。</returns>
public static int RandomByWeight(int[] weights)
{
var sum = weights.Sum();
var numberRand = Rand(sum);
var sumTemp = 0;
for (var i = 0; i < weights.Length; i++)
{
sumTemp += weights[i];
if (numberRand <= sumTemp)
{
return i;
}
}
return -1;
}
/// <summary>
/// 根据固定概率随机选择一个索引,即某个数值上限内随机多少次。
/// </summary>
/// <param name="args">概率数组,每个元素表示相应索引的概率。</param>
/// <returns>随机选择的索引值。</returns>
public static int RandomByFixedProbability(int[] args)
{
var random = _random ??= new Random();
var argCount = args.Length;
var sum = args.Sum();
var value = random.NextDouble() * sum;
while (sum > value)
{
sum -= args[argCount - 1];
argCount--;
}
return argCount;
}
/// <summary>
/// 返回随机数。
/// </summary>
/// <param name="containNegative">是否包含负数。</param>
/// <returns>返回一个随机的单精度浮点数。</returns>
public static float NextFloat(bool containNegative = false)
{
var random = _random ??= new Random();
float f;
var buffer = new FixedBytes4().AsSpan();
if (containNegative)
{
do
{
random.NextBytes(buffer);
f = BitConverter.ToSingle(buffer);
} while ((f >= float.MinValue && f < float.MaxValue) == false);
return f;
}
do
{
random.NextBytes(buffer);
f = BitConverter.ToSingle(buffer);
} while ((f >= 0 && f < float.MaxValue) == false);
return f;
}
/// <summary>
/// 返回一个小于所指定最大值的非负随机数。
/// </summary>
/// <param name="maxValue">要生成的随机数的上限(随机数不能取该上限值)。 maxValue 必须大于或等于零。</param>
/// <returns>大于等于零且小于 maxValue 的单精度浮点数,即:返回值的范围通常包括零但不包括 maxValue。 不过,如果 maxValue 等于零,则返回 maxValue。</returns>
public static float NextFloat(float maxValue)
{
if (maxValue.Equals(0))
{
return maxValue;
}
if (maxValue < 0)
{
throw new ArgumentOutOfRangeException("“maxValue”必须大于 0。", "maxValue");
}
var random = _random ??= new Random();
float f;
var buffer = new FixedBytes4().AsSpan();
do
{
random.NextBytes(buffer);
f = BitConverter.ToSingle(buffer);
} while ((f >= 0 && f < maxValue) == false);
return f;
}
/// <summary>
/// 返回一个指定范围内的随机数。
/// </summary>
/// <param name="minValue">返回的随机数的下界(随机数可取该下界值)。</param>
/// <param name="maxValue">返回的随机数的上界(随机数不能取该上界值)。 maxValue 必须大于或等于 minValue。</param>
/// <returns>一个大于等于 minValue 且小于 maxValue 的单精度浮点数,即:返回的值范围包括 minValue 但不包括 maxValue。 如果 minValue 等于 maxValue则返回 minValue。</returns>
public static float NextFloat(float minValue, float maxValue)
{
if (minValue.Equals(maxValue))
{
return minValue;
}
if (minValue > maxValue)
{
throw new ArgumentOutOfRangeException("“minValue”不能大于 maxValue。", "minValue");
}
var random = _random ??= new Random();
var buffer = new FixedBytes4().AsSpan();
float f;
do
{
random.NextBytes(buffer);
f = BitConverter.ToSingle(buffer);
} while ((f >= minValue && f < maxValue) == false);
return f;
}
/// <summary>
/// 在指定的矩形区域内随机生成一个二维向量位置。
/// </summary>
/// <param name="minX">X轴最小值。</param>
/// <param name="maxX">X轴最大值。</param>
/// <param name="minY">Y轴最小值。</param>
/// <param name="maxY">Y轴最大值。</param>
/// <returns>随机生成的二维向量位置。</returns>
public static Vector2 NextVector2(float minX, float maxX, float minY, float maxY)
{
return new Vector2(NextFloat(minX, maxX), NextFloat(minY, maxY));
}
/// <summary>
/// 生成指定长度的随机数字代码。
/// </summary>
/// <param name="len">数字代码的长度。</param>
/// <returns>生成的随机数字代码。</returns>
public static string RandomNumberCode(int len = 6)
{
int num = 0;
for (int i = 0; i < len; i++)
{
int number = RandomNumber(0, 10);
num = num * 10 + number;
}
return num.ToString();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5ca1a2a2ac7a7472ab9c07166a4e471a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,295 @@
#if FANTASY_WEBGL
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace Fantasy.Helper
{
/// <summary>
/// 随机数操作助手类,提供各种随机数生成和操作方法。
/// </summary>
public static partial class RandomHelper
{
private static readonly Random Random = new Random();
private static readonly byte[] Byte8 = new byte[8];
private static readonly byte[] Byte2 = new byte[2];
/// <summary>
/// 生成一个随机的无符号 64 位整数。
/// </summary>
/// <returns>无符号 64 位整数。</returns>
public static ulong RandUInt64()
{
Random.NextBytes(Byte8);
return BitConverter.ToUInt64(Byte8, 0);
}
/// <summary>
/// 生成一个随机的 64 位整数。
/// </summary>
/// <returns>64 位整数。</returns>
public static long RandInt64()
{
Random.NextBytes(Byte8);
return BitConverter.ToInt64(Byte8, 0);
}
/// <summary>
/// 生成一个随机的无符号 32 位整数。
/// </summary>
/// <returns>无符号 32 位整数。</returns>
public static uint RandUInt32()
{
return (uint) Random.Next();
}
/// <summary>
/// 生成一个随机的无符号 16 位整数。
/// </summary>
/// <returns>无符号 16 位整数。</returns>
public static ushort RandUInt16()
{
Random.NextBytes(Byte2);
return BitConverter.ToUInt16(Byte2, 0);
}
/// <summary>
/// 在指定范围内生成一个随机整数(包含下限,不包含上限)。
/// </summary>
/// <param name="lower">下限。</param>
/// <param name="upper">上限。</param>
/// <returns>生成的随机整数。</returns>
public static int RandomNumber(int lower, int upper)
{
return Random.Next(lower, upper);
}
/// <summary>
/// 生成一个随机的布尔值。
/// </summary>
/// <returns>随机的布尔值。</returns>
public static bool RandomBool()
{
return Random.Next(2) == 0;
}
/// <summary>
/// 从数组中随机选择一个元素。
/// </summary>
/// <typeparam name="T">数组元素的类型。</typeparam>
/// <param name="array">要选择的数组。</param>
/// <returns>随机选择的数组元素。</returns>
public static T RandomArray<T>(this T[] array)
{
return array[RandomNumber(0, array.Count())];
}
/// <summary>
/// 从列表中随机选择一个元素。
/// </summary>
/// <typeparam name="T">列表元素的类型。</typeparam>
/// <param name="array">要选择的列表。</param>
/// <returns>随机选择的列表元素。</returns>
public static T RandomArray<T>(this List<T> array)
{
return array[RandomNumber(0, array.Count())];
}
/// <summary>
/// 打乱列表中元素的顺序。
/// </summary>
/// <typeparam name="T">列表元素的类型。</typeparam>
/// <param name="arr">要打乱顺序的列表。</param>
public static void BreakRank<T>(List<T> arr)
{
if (arr == null || arr.Count < 2)
{
return;
}
for (var i = 0; i < arr.Count / 2; i++)
{
var index = Random.Next(0, arr.Count);
(arr[index], arr[arr.Count - index - 1]) = (arr[arr.Count - index - 1], arr[index]);
}
}
/// <summary>
/// 生成一个介于 0 和 1 之间的随机单精度浮点数。
/// </summary>
/// <returns>随机单精度浮点数。</returns>
public static float RandFloat01()
{
var value = Random.NextDouble();
return (float) value;
}
private static int Rand(int n)
{
var rd = new Random();
// 注意返回值是左闭右开所以maxValue要加1
return rd.Next(1, n + 1);
}
/// <summary>
/// 根据权重随机选择一个索引。
/// </summary>
/// <param name="weights">权重数组,每个元素表示相应索引的权重。</param>
/// <returns>随机选择的索引值。</returns>
public static int RandomByWeight(int[] weights)
{
var sum = weights.Sum();
var numberRand = Rand(sum);
var sumTemp = 0;
for (var i = 0; i < weights.Length; i++)
{
sumTemp += weights[i];
if (numberRand <= sumTemp)
{
return i;
}
}
return -1;
}
/// <summary>
/// 根据固定概率随机选择一个索引,即某个数值上限内随机多少次。
/// </summary>
/// <param name="args">概率数组,每个元素表示相应索引的概率。</param>
/// <returns>随机选择的索引值。</returns>
public static int RandomByFixedProbability(int[] args)
{
var argCount = args.Length;
var sum = args.Sum();
var random = Random.NextDouble() * sum;
while (sum > random)
{
sum -= args[argCount - 1];
argCount--;
}
return argCount;
}
/// <summary>
/// 返回随机数。
/// </summary>
/// <param name="containNegative">是否包含负数。</param>
/// <returns>返回一个随机的单精度浮点数。</returns>
public static float NextFloat(bool containNegative = false)
{
float f;
var buffer = new byte[4];
if (containNegative)
{
do
{
Random.NextBytes(buffer);
f = BitConverter.ToSingle(buffer, 0);
} while ((f >= float.MinValue && f < float.MaxValue) == false);
return f;
}
do
{
Random.NextBytes(buffer);
f = BitConverter.ToSingle(buffer, 0);
} while ((f >= 0 && f < float.MaxValue) == false);
return f;
}
/// <summary>
/// 返回一个小于所指定最大值的非负随机数。
/// </summary>
/// <param name="maxValue">要生成的随机数的上限(随机数不能取该上限值)。 maxValue 必须大于或等于零。</param>
/// <returns>大于等于零且小于 maxValue 的单精度浮点数,即:返回值的范围通常包括零但不包括 maxValue。 不过,如果 maxValue 等于零,则返回 maxValue。</returns>
public static float NextFloat(float maxValue)
{
if (maxValue.Equals(0))
{
return maxValue;
}
if (maxValue < 0)
{
throw new ArgumentOutOfRangeException("“maxValue”必须大于 0。", "maxValue");
}
float f;
var buffer = new byte[4];
do
{
Random.NextBytes(buffer);
f = BitConverter.ToSingle(buffer, 0);
} while ((f >= 0 && f < maxValue) == false);
return f;
}
/// <summary>
/// 返回一个指定范围内的随机数。
/// </summary>
/// <param name="minValue">返回的随机数的下界(随机数可取该下界值)。</param>
/// <param name="maxValue">返回的随机数的上界(随机数不能取该上界值)。 maxValue 必须大于或等于 minValue。</param>
/// <returns>一个大于等于 minValue 且小于 maxValue 的单精度浮点数,即:返回的值范围包括 minValue 但不包括 maxValue。 如果 minValue 等于 maxValue则返回 minValue。</returns>
public static float NextFloat(float minValue, float maxValue)
{
if (minValue.Equals(maxValue))
{
return minValue;
}
if (minValue > maxValue)
{
throw new ArgumentOutOfRangeException("“minValue”不能大于 maxValue。", "minValue");
}
float f;
var buffer = new byte[4];
do
{
Random.NextBytes(buffer);
f = BitConverter.ToSingle(buffer, 0);
} while ((f >= minValue && f < maxValue) == false);
return f;
}
/// <summary>
/// 在指定的矩形区域内随机生成一个二维向量位置。
/// </summary>
/// <param name="minX">X轴最小值。</param>
/// <param name="maxX">X轴最大值。</param>
/// <param name="minY">Y轴最小值。</param>
/// <param name="maxY">Y轴最大值。</param>
/// <returns>随机生成的二维向量位置。</returns>
public static Vector2 NextVector2(float minX, float maxX, float minY, float maxY)
{
return new Vector2(NextFloat(minX, maxX), NextFloat(minY, maxY));
}
/// <summary>
/// 生成指定长度的随机数字代码。
/// </summary>
/// <param name="len">数字代码的长度。</param>
/// <returns>生成的随机数字代码。</returns>
public static string RandomNumberCode(int len = 6)
{
int num = 0;
for (int i = 0; i < len; i++)
{
int number = RandomNumber(0, 10);
num = num * 10 + number;
}
return num.ToString();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 74eea4430a34f4086a0f6e09f1e4ccfb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,70 @@
#if !FANTASY_WEBGL
using System.Net;
using System.Net.Sockets;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
namespace NBC.Helper
{
/// <summary>
/// Socket帮助类
/// </summary>
public static partial class SocketHelper
{
// always pass the same IPEndPointNonAlloc instead of allocating a new
// one each time.
//
// use IPEndPointNonAlloc.temp to get the latest SocketAdddress written
// by ReceiveFrom_Internal!
//
// IMPORTANT: .temp will be overwritten in next call!
// hash or manually copy it if you need to store it, e.g.
// when adding a new connection.
public static int ReceiveFrom_NonAlloc(
this Socket socket,
byte[] buffer,
int offset,
int size,
SocketFlags socketFlags,
EndPoint remoteEndPoint)
{
// call ReceiveFrom with IPEndPointNonAlloc.
// need to wrap this in ReceiveFrom_NonAlloc because it's not
// obvious that IPEndPointNonAlloc.Create does NOT create a new
// IPEndPoint. it saves the result in IPEndPointNonAlloc.temp!
EndPoint casted = remoteEndPoint;
return socket.ReceiveFrom(buffer, offset, size, socketFlags, ref casted);
}
// same as above, different parameters
public static int ReceiveFrom_NonAlloc(this Socket socket, byte[] buffer, ref EndPoint remoteEndPoint)
{
#if UNITY
EndPoint casted = remoteEndPoint;
return socket.ReceiveFrom(buffer, ref casted);
#else
return socket.ReceiveFrom(buffer, ref remoteEndPoint);
#endif
}
// SendTo allocates too:
// https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L2240
// -> the allocation is in EndPoint.Serialize()
// NOTE: technically this function isn't necessary.
// could just pass IPEndPointNonAlloc.
// still good for strong typing.
//public static int SendTo_NonAlloc(
// this Socket socket,
// byte[] buffer,
// int offset,
// int size,
// SocketFlags socketFlags,
// IPEndPointNonAlloc remoteEndPoint)
//{
// EndPoint casted = remoteEndPoint;
// return socket.SendTo(buffer, offset, size, socketFlags, casted);
//}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 54d75f1a06d9144e482ec46a67f2cfeb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,80 @@
using System;
using UnityEngine;
namespace NBC.Helper
{
/// <summary>
/// 提供与时间相关的帮助方法。
/// </summary>
public static partial class TimeHelper
{
/// <summary>
/// 一小时的毫秒值。
/// </summary>
public const long Hour = 3600000;
/// <summary>
/// 一分钟的毫秒值。
/// </summary>
public const long Minute = 60000;
/// <summary>
/// 一天的毫秒值。
/// </summary>
public const long OneDay = 86400000;
// 1970年1月1日的Ticks
private const long Epoch = 621355968000000000L;
/// <summary>
/// 获取当前时间的毫秒数从1970年1月1日开始计算。
/// </summary>
public static long Now => (DateTime.UtcNow.Ticks - Epoch) / 10000;
/// <summary>
/// 与服务器时间的偏差。
/// </summary>
public static long TimeDiff;
/// <summary>
/// 获取当前服务器时间的毫秒数,加上与服务器时间的偏差。
/// </summary>
public static long ServerNow => Now + TimeDiff;
/// <summary>
/// 获取当前Unity运行的总时间的毫秒数。
/// </summary>
public static long UnityNow => (long) (Time.time * 1000);
/// <summary>
/// 根据时间获取时间戳
/// </summary>
public static long Transition(DateTime dateTime)
{
return (dateTime.ToUniversalTime().Ticks - Epoch) / 10000;
}
/// <summary>
/// 根据时间获取 时间戳
/// </summary>
public static long TransitionToSeconds(DateTime dateTime)
{
return (dateTime.ToUniversalTime().Ticks - Epoch) / 10000000;
}
/// <summary>
/// 将毫秒数转换为日期时间。
/// </summary>
/// <param name="timeStamp">要转换的毫秒数。</param>
/// <returns>转换后的日期时间。</returns>
public static DateTime Transition(this long timeStamp)
{
return new DateTime(Epoch + timeStamp * 10000, DateTimeKind.Utc).ToUniversalTime();
}
/// <summary>
/// 将毫秒数转换为本地时间的日期时间。
/// </summary>
/// <param name="timeStamp">要转换的毫秒数。</param>
/// <returns>转换后的本地时间的日期时间。</returns>
public static DateTime TransitionLocal(this long timeStamp)
{
return new DateTime(Epoch + timeStamp * 10000, DateTimeKind.Utc).ToLocalTime();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3937c5cea56304a79b138c980c91b79b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1d9aa49407518439ebc2527068436501
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,242 @@
using System;
using NBC.Async;
using UnityEngine;
using UnityEngine.Networking;
namespace NBC.Unity
{
/// <summary>
/// UnityWebRequest的帮助类
/// </summary>
public static class UnityWebRequestHelper
{
/// <summary>
/// 获取一个文本
/// </summary>
/// <param name="url"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static FTask<string> GetText(string url, FCancellationToken cancellationToken = null)
{
var task = FTask<string>.Create(false);
var unityWebRequest = UnityWebRequest.Get(url);
var unityWebRequestAsyncOperation = unityWebRequest.SendWebRequest();
if (cancellationToken != null)
{
cancellationToken.Add(() =>
{
unityWebRequest.Abort();
task.SetResult(null);
});
}
unityWebRequestAsyncOperation.completed += operation =>
{
if (unityWebRequest.result == UnityWebRequest.Result.Success)
{
var text = unityWebRequest.downloadHandler.text;
task.SetResult(text);
}
else
{
Log.Error(unityWebRequest.error);
task.SetResult(null);
}
};
return task;
}
/// <summary>
/// 获取一个Sprite
/// </summary>
/// <param name="url"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static FTask<Sprite> GetSprite(string url, FCancellationToken cancellationToken = null)
{
var task = FTask<Sprite>.Create(false);
var unityWebRequest = UnityWebRequestTexture.GetTexture(Uri.EscapeUriString(url));
var unityWebRequestAsyncOperation = unityWebRequest.SendWebRequest();
if (cancellationToken != null)
{
cancellationToken.Add(() =>
{
unityWebRequest.Abort();
task.SetResult(null);
});
}
unityWebRequestAsyncOperation.completed += operation =>
{
if (unityWebRequest.result == UnityWebRequest.Result.Success)
{
var texture = DownloadHandlerTexture.GetContent(unityWebRequest);
var sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.one * 5, 1f);
task.SetResult(sprite);
}
else
{
Log.Error(unityWebRequest.error);
task.SetResult(null);
}
};
return task;
}
/// <summary>
/// 获取一个Texture
/// </summary>
/// <param name="url"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static FTask<Texture> GetTexture(string url, FCancellationToken cancellationToken = null)
{
var task = FTask<Texture>.Create(false);
var unityWebRequest = UnityWebRequestTexture.GetTexture(Uri.EscapeUriString(url));
var unityWebRequestAsyncOperation = unityWebRequest.SendWebRequest();
if (cancellationToken != null)
{
cancellationToken.Add(() =>
{
unityWebRequest.Abort();
task.SetResult(null);
});
}
unityWebRequestAsyncOperation.completed += operation =>
{
if (unityWebRequest.result == UnityWebRequest.Result.Success)
{
var texture = DownloadHandlerTexture.GetContent(unityWebRequest);
task.SetResult(texture);
}
else
{
Log.Error(unityWebRequest.error);
task.SetResult(null);
}
};
return task;
}
/// <summary>
/// 获取Bytes
/// </summary>
/// <param name="url"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static FTask<byte[]> GetBytes(string url, FCancellationToken cancellationToken = null)
{
var task = FTask<byte[]>.Create(false);
var unityWebRequest = UnityWebRequest.Get(url);
var unityWebRequestAsyncOperation = unityWebRequest.SendWebRequest();
if (cancellationToken != null)
{
cancellationToken.Add(() =>
{
unityWebRequest.Abort();
task.SetResult(null);
});
}
unityWebRequestAsyncOperation.completed += operation =>
{
if (unityWebRequest.result == UnityWebRequest.Result.Success)
{
var bytes = unityWebRequest.downloadHandler.data;
task.SetResult(bytes);
}
else
{
Log.Error(unityWebRequest.error);
task.SetResult(null);
}
};
return task;
}
/// <summary>
/// 获取AssetBundle
/// </summary>
/// <param name="url"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static FTask<AssetBundle> GetAssetBundle(string url, FCancellationToken cancellationToken = null)
{
var task = FTask<AssetBundle>.Create(false);
var unityWebRequest = UnityWebRequestAssetBundle.GetAssetBundle(Uri.EscapeUriString(url));
var unityWebRequestAsyncOperation = unityWebRequest.SendWebRequest();
if (cancellationToken != null)
{
cancellationToken.Add(() =>
{
unityWebRequest.Abort();
task.SetResult(null);
});
}
unityWebRequestAsyncOperation.completed += operation =>
{
if (unityWebRequest.result == UnityWebRequest.Result.Success)
{
var assetBundle = DownloadHandlerAssetBundle.GetContent(unityWebRequest);
task.SetResult(assetBundle);
return;
}
Log.Error(unityWebRequest.error);
task.SetResult(null);
};
return task;
}
/// <summary>
/// 获取AudioClip
/// </summary>
/// <param name="url"></param>
/// <param name="audioType"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static FTask<AudioClip> GetAudioClip(string url, AudioType audioType, FCancellationToken cancellationToken = null)
{
var task = FTask<AudioClip>.Create(false);
var unityWebRequest = UnityWebRequestMultimedia.GetAudioClip(Uri.EscapeUriString(url), audioType);
var unityWebRequestAsyncOperation = unityWebRequest.SendWebRequest();
if (cancellationToken != null)
{
cancellationToken.Add(() =>
{
unityWebRequest.Abort();
task.SetResult(null);
});
}
unityWebRequestAsyncOperation.completed += operation =>
{
if (unityWebRequest.result == UnityWebRequest.Result.Success)
{
var audioClip = DownloadHandlerAudioClip.GetContent(unityWebRequest);
task.SetResult(audioClip);
}
else
{
Log.Error(unityWebRequest.error);
task.SetResult(null);
}
};
return task;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4a679bf05117455388666f6d8cc35d7d
timeCreated: 1726022012

View File

@@ -0,0 +1,38 @@
using System;
using System.Runtime.CompilerServices;
namespace NBC.Helper
{
/// <summary>
/// WebSocket帮助类
/// </summary>
public static partial class WebSocketHelper
{
/// <summary>
/// 根据字符串获取WebSocket的连接地址
/// </summary>
/// <param name="address">目标服务器地址格式为:127.0.0.1:2000</param>
/// <param name="isHttps">目标服务器是否为加密连接也就是https</param>
/// <returns></returns>
/// <exception cref="FormatException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetWebSocketAddress(string address, bool isHttps)
{
var addressSplit = address.Split(':');
if (addressSplit.Length != 2)
{
throw new FormatException("Invalid format");
}
var ipString = addressSplit[0];
var portString = addressSplit[1];
if (!int.TryParse(portString, out var port) || port < 0 || port > 65535)
{
throw new FormatException("Invalid port number");
}
return isHttps ? $"wss://{ipString}:{portString}" : $"ws://{ipString}:{portString}";
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 43d82d55edae640d69ed83a832d4dd3e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
using System.Runtime.InteropServices;
namespace NBC.Helper
{
/// <summary>
/// 精度设置
/// </summary>
public static partial class WinPeriod
{
// 一般默认的精度不止1毫秒不同操作系统有所不同需要调用timeBeginPeriod与timeEndPeriod来设置精度
[DllImport("winmm")]
private static extern void timeBeginPeriod(int t);
/// <summary>
/// 针对Windows平台设置精度
/// </summary>
public static void Initialize()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
timeBeginPeriod(1);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 20f6f771c52ad42f0a0e74df662f45cc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: