Compare commits

..

44 Commits

Author SHA1 Message Date
ALEXTANG
fb26ea2297 Update AddressableManageComponent.cs 2023-08-04 01:42:26 +08:00
ALEXTANG
9ac150425f Update IntDictionaryConfig.cs 2023-08-04 01:42:00 +08:00
ALEXTANG
36d2c146b0 1、修复了MongoDB在2.18.0以后需要自定义注册ObjectSerializer的问题。 2、Addressable的AddAddressable接口增加isLock参数、用来决定是否需要添加携程锁。 3、修复了APackInfo因为网络多线程的原因导致线程安全的问题。
1、修复了MongoDB在2.18.0以后需要自定义注册ObjectSerializer的问题。
2、Addressable的AddAddressable接口增加isLock参数、用来决定是否需要添加携程锁。
3、修复了APackInfo因为网络多线程的原因导致线程安全的问题。
2023-08-04 01:41:31 +08:00
ALEXTANG
774b73bbbf 注释防裁剪脚本中相机权限的引用
注释防裁剪脚本中相机权限的引用
2023-08-01 16:57:43 +08:00
ALEXTANG
32366eb127 移除防裁剪脚本中相机权限的引用
移除防裁剪脚本中相机权限的引用
2023-08-01 16:54:49 +08:00
ALEXTANG
a843617e5f Update README.md 2023-08-01 10:44:15 +08:00
ALEXTANGXIAO
46b139f7cf 补充IOS运行快照到Books下
补充IOS运行快照到Books下
2023-08-01 00:06:07 +08:00
ALEXTANG
e6ff1dec3f Update BuglyAgent.cs 2023-07-31 20:36:44 +08:00
ALEXTANG
4429732010 Update ConfigLoader.cs 2023-07-31 15:01:11 +08:00
ALEXTANGXIAO
60a5caebae 1、Scene.Create接口增加了一个泛型方法接口。 2、SceneConfig增加了SceneTypeConfig和SceneSubType
1、Scene.Create接口增加了一个泛型方法接口。 2、SceneConfig增加了SceneTypeConfig和SceneSubType
2023-07-29 01:03:35 +08:00
ALEXTANGXIAO
f7c95d8216 服务器启动防呆提示需要转表
服务器启动防呆提示需要转表
2023-07-29 00:12:52 +08:00
ALEXTANGXIAO
98dcb80942 更新编辑器MenuItem导表与导出协议
更新编辑器MenuItem导表与导出协议
2023-07-29 00:03:54 +08:00
ALEXTANG
30192d52cf 调整网络demo场景路径
调整网络demo场景路径
2023-07-27 10:18:02 +08:00
ALEXTANGXIAO
b7b2262d53 合理化服务器命名,使开发者更容易理解。
Scene的routeId更名为locationId
2023-07-27 00:33:39 +08:00
ALEXTANG
d1c93f15d6 Application.OpenURL 调用bat的cmd运行时问题
Application.OpenURL 调用bat的cmd运行时问题
2023-07-26 15:39:20 +08:00
ALEXTANGXIAO
1ac1ff7d56 [+] Update HybridCLR v3.4.0
[+] Update HybridCLR v3.4.0
2023-07-26 00:07:47 +08:00
ALEXTANG
863788f303 Update UIWidget.cs 2023-07-25 23:37:02 +08:00
ALEXTANG
74790c7486 Update UIWindow.cs 2023-07-25 23:34:42 +08:00
ALEXTANG
1c223c8ad0 [+] DOTweenExtension
[+] DOTweenExtension
2023-07-25 17:08:57 +08:00
ALEXTANG
e75b3a4e66 [+] UniTask External YooAsset、Dotween、TextMeshPro 2023-07-25 16:28:09 +08:00
ALEXTANG
91b0995911 [+] UniTask External YooAsset、Dotween、TextMeshPro
[+] UniTask External YooAsset、Dotween、TextMeshPro
2023-07-25 16:22:13 +08:00
ALEXTANG
14c886ea8f [+] UniTask External YooAsset、Dotween、TextMeshPro
[+] UniTask External YooAsset、Dotween、TextMeshPro
2023-07-25 16:20:26 +08:00
ALEXTANG
9babc0ba85 [+] 合理化目录结构 精简非必要插件如确定性物理库以及Recast插件。
[+] 合理化目录结构 精简非必要插件如确定性物理库以及Recast插件。
2023-07-25 14:34:05 +08:00
ALEXTANG
f8056aef32 调整服务器逻辑
1、调整Entity.AddComponent方法添加的组件Id为父组件的Id。
2、增加了ISupportedSingleCollection和ISingleCollectionRoot接口.
3、增加了SingleCollection、用于把子组件保存到单独的数据库表的功能,配合ISupportedSingleCollection和ISingleCollectionRoot。
2023-07-24 16:49:23 +08:00
ALEXTANG
846dc4d4bc Fixed the session dispose
Fixed the session dispose
2023-07-24 13:17:59 +08:00
ALEXTANG
03ab7fb353 Create start_export.bat 2023-07-23 22:18:47 +08:00
ALEXTANG
3c11980e7c Create start_develop.bat 2023-07-23 22:18:45 +08:00
ALEXTANG
c23aa0bd71 Revert "Update Core.csproj"
This reverts commit df76d0b77a.
2023-07-23 21:32:22 +08:00
ALEXTANG
df76d0b77a Update Core.csproj 2023-07-23 21:32:01 +08:00
ALEXTANG
35d2012546 1.替换了KCP为C#版本 2.KCP增加了Memery<T>支持。 3.框架增加了对Memery<T>的支持,服务器网络消息采用Memery<T>,性能得大幅度的提升 4.优化了消息调度执行逻辑
1.替换了KCP为C#版本
2.KCP增加了Memery<T>支持。
3.框架增加了对Memery<T>的支持,服务器网络消息采用Memery<T>,性能得大幅度的提升
4.优化了消息调度执行逻辑
2023-07-23 00:25:47 +08:00
ALEXTANG
c96d20a89a [+] GameFrameworkModuleSystem
[+] GameFrameworkModuleSystem
2023-07-22 17:53:00 +08:00
ALEXTANG
5787d0f9dc [+] GameModuleSystem
[+] GameModuleSystem
2023-07-22 17:51:24 +08:00
ALEXTANG
8b35c8ca07 Update ProjectSettings.asset 2023-07-22 17:50:06 +08:00
ALEXTANG
e5456da482 合理化Address协议逻辑
合理化Address协议逻辑
2023-07-21 17:50:47 +08:00
ALEXTANG
144ba9f222 完善AddressableManageComponent与ClientNetworkComponent
完善AddressableManageComponent与ClientNetworkComponent
2023-07-21 14:32:20 +08:00
ALEXTANG
889fbdc8e1 Update UIGaussianBlurLayer.cs 2023-07-19 17:34:02 +08:00
ALEXTANG
0d177e6868 Update AssetGroup.cs 2023-07-19 17:33:56 +08:00
ALEXTANG
29135228be Update AssetReference.cs 2023-07-19 17:33:52 +08:00
ALEXTANG
c1a1de73cd Update ResourceManager.cs 2023-07-19 17:33:48 +08:00
ALEXTANG
612e9b7eba Update ResourceModule.cs 2023-07-19 17:33:44 +08:00
ALEXTANG
5ed6b8c378 Update UIModule.cs 2023-07-19 17:33:41 +08:00
ALEXTANG
06dad5a68a Update UIWidget.cs 2023-07-19 17:33:28 +08:00
ALEXTANG
9e0462043c Update WindowAttribute.cs 2023-07-19 17:33:25 +08:00
ALEXTANG
c1178e284b Update DUnityUtil.cs 2023-07-19 17:33:21 +08:00
519 changed files with 5106 additions and 86935 deletions

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c930bcf75e933064db8511b88e39d216
guid: afddbfe87a53e9049bf031ee18842f87
folderAsset: yes
DefaultImporter:
externalObjects: {}

Binary file not shown.

View File

@@ -1,6 +1,6 @@
fileFormatVersion: 2
guid: d020df1f2b63b444e8ca93c0d88597e2
AssemblyDefinitionImporter:
guid: 8f3bcefaf67e76141a6d8edeb8354fea
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:

Binary file not shown.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 25113973d38eb5a48b064863830539a4
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ef51ed54e72f97a4ab530b932680e08b
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 090163c612f34ac4fb80004ac5f057b4
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -63,6 +63,14 @@ namespace TEngine.Core
LoadAssembly(assemblyName, assembly);
}
public static IEnumerable<int> ForEachAssemblyName()
{
foreach (var (key, _) in AssemblyList)
{
yield return key;
}
}
public static IEnumerable<Type> ForEach()
{

View File

@@ -6,5 +6,7 @@ namespace TEngine.Core
public const uint ErrNotFoundRoute = 100000003; // 没有找到Route消息
public const uint ErrRouteTimeout = 100000004; // 发送Route消息超时
public const uint Error_NotFindEntity = 100000008; // 没有找到Entity
public const uint Error_CopyTimeout = 100000009; // CopyTimeout不能小于或等于0
public const uint Error_Transfer = 100000010;// 传送发生了错误
}
}

View File

@@ -6,11 +6,13 @@ namespace TEngine.Core.DataBase;
public interface IDateBase
{
public static readonly CoroutineLockQueueType DataBaseLock = new CoroutineLockQueueType("DataBaseLock");
IDateBase Initialize(string connectionString, string dbName);
FTask<long> Count<T>(string collection = null) where T : Entity;
FTask<long> Count<T>(Expression<Func<T, bool>> filter, string collection = null) where T : Entity;
FTask<bool> Exist<T>(string collection = null) where T : Entity;
FTask<bool> Exist<T>(Expression<Func<T, bool>> filter, string collection = null) where T : Entity;
FTask<T> QueryNotLock<T>(long id, string collection = null) where T : Entity;
FTask<T> Query<T>(long id, string collection = null) where T : Entity;
FTask<(int count, List<T> dates)> QueryCountAndDatesByPage<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, string collection = null) where T : Entity;
FTask<(int count, List<T> dates)> QueryCountAndDatesByPage<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, string[] cols, string collection = null) where T : Entity;

View File

@@ -13,7 +13,6 @@ public sealed class MongoDataBase : IDateBase
private MongoClient _mongoClient;
private IMongoDatabase _mongoDatabase;
private readonly HashSet<string> _collections = new HashSet<string>();
private readonly CoroutineLockQueueType _mongoDataBaseLock = new CoroutineLockQueueType("MongoDataBaseLock");
public IDateBase Initialize(string connectionString, string dbName)
{
@@ -86,10 +85,16 @@ public sealed class MongoDataBase : IDateBase
#endregion
#region Query
public async FTask<T> QueryNotLock<T>(long id, string collection = null) where T : Entity
{
var cursor = await GetCollection<T>(collection).FindAsync(d => d.Id == id);
var v = await cursor.FirstOrDefaultAsync();
return v;
}
public async FTask<T> Query<T>(long id, string collection = null) where T : Entity
{
using (await _mongoDataBaseLock.Lock(id))
using (await IDateBase.DataBaseLock.Lock(id))
{
var cursor = await GetCollection<T>(collection).FindAsync(d => d.Id == id);
var v = await cursor.FirstOrDefaultAsync();
@@ -99,7 +104,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<(int count, List<T> dates)> QueryCountAndDatesByPage<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, string collection = null) where T : Entity
{
using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64()))
using (await IDateBase.DataBaseLock.Lock(RandomHelper.RandInt64()))
{
var count = await Count(filter);
var dates = await QueryByPage(filter, pageIndex, pageSize, collection);
@@ -109,7 +114,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<(int count, List<T> dates)> QueryCountAndDatesByPage<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, string[] cols, string collection = null) where T : Entity
{
using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64()))
using (await IDateBase.DataBaseLock.Lock(RandomHelper.RandInt64()))
{
var count = await Count(filter);
@@ -121,7 +126,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<List<T>> QueryByPage<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, string collection = null) where T : Entity
{
using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64()))
using (await IDateBase.DataBaseLock.Lock(RandomHelper.RandInt64()))
{
return await GetCollection<T>(collection).Find(filter).Skip((pageIndex - 1) * pageSize)
.Limit(pageSize)
@@ -131,7 +136,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<List<T>> QueryByPage<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, string[] cols, string collection = null) where T : Entity
{
using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64()))
using (await IDateBase.DataBaseLock.Lock(RandomHelper.RandInt64()))
{
var projection = Builders<T>.Projection.Include("");
@@ -147,7 +152,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<List<T>> QueryByPageOrderBy<T>(Expression<Func<T, bool>> filter, int pageIndex, int pageSize, Expression<Func<T, object>> orderByExpression, bool isAsc = true, string collection = null) where T : Entity
{
using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64()))
using (await IDateBase.DataBaseLock.Lock(RandomHelper.RandInt64()))
{
if (isAsc)
{
@@ -162,7 +167,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<T> First<T>(Expression<Func<T, bool>> filter, string collection = null) where T : Entity
{
using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64()))
using (await IDateBase.DataBaseLock.Lock(RandomHelper.RandInt64()))
{
var cursor = await GetCollection<T>(collection).FindAsync(filter);
@@ -172,7 +177,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<T> First<T>(string json, string[] cols, string collection = null) where T : Entity
{
using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64()))
using (await IDateBase.DataBaseLock.Lock(RandomHelper.RandInt64()))
{
var projection = Builders<T>.Projection.Include("");
@@ -193,7 +198,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<T> Last<T>(Expression<Func<T, bool>> filter, string collection = null) where T : Entity
{
using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64()))
using (await IDateBase.DataBaseLock.Lock(RandomHelper.RandInt64()))
{
var cursor = await GetCollection<T>(collection).FindAsync(filter);
@@ -205,7 +210,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<List<T>> QueryOrderBy<T>(Expression<Func<T, bool>> filter, Expression<Func<T, object>> orderByExpression, bool isAsc = true, string collection = null) where T : Entity
{
using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64()))
using (await IDateBase.DataBaseLock.Lock(RandomHelper.RandInt64()))
{
if (isAsc)
{
@@ -219,7 +224,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<List<T>> Query<T>(Expression<Func<T, bool>> filter, string collection = null) where T : Entity
{
using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64()))
using (await IDateBase.DataBaseLock.Lock(RandomHelper.RandInt64()))
{
var cursor = await GetCollection<T>(collection).FindAsync(filter);
var v = await cursor.ToListAsync();
@@ -229,7 +234,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask Query(long id, List<string> collectionNames, List<Entity> result)
{
using (await _mongoDataBaseLock.Lock(id))
using (await IDateBase.DataBaseLock.Lock(id))
{
if (collectionNames == null || collectionNames.Count == 0)
{
@@ -254,7 +259,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<List<T>> QueryJson<T>(string json, string collection = null) where T : Entity
{
using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64()))
using (await IDateBase.DataBaseLock.Lock(RandomHelper.RandInt64()))
{
FilterDefinition<T> filterDefinition = new JsonFilterDefinition<T>(json);
var cursor = await GetCollection<T>(collection).FindAsync(filterDefinition);
@@ -265,7 +270,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<List<T>> QueryJson<T>(string json, string[] cols, string collection = null) where T : Entity
{
using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64()))
using (await IDateBase.DataBaseLock.Lock(RandomHelper.RandInt64()))
{
var projection = Builders<T>.Projection.Include("");
@@ -286,7 +291,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<List<T>> QueryJson<T>(long taskId, string json, string collection = null) where T : Entity
{
using (await _mongoDataBaseLock.Lock(taskId))
using (await IDateBase.DataBaseLock.Lock(taskId))
{
FilterDefinition<T> filterDefinition = new JsonFilterDefinition<T>(json);
var cursor = await GetCollection<T>(collection).FindAsync(filterDefinition);
@@ -297,7 +302,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<List<T>> Query<T>(Expression<Func<T, bool>> filter, string[] cols, string collection = null) where T : class
{
using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64()))
using (await IDateBase.DataBaseLock.Lock(RandomHelper.RandInt64()))
{
var projection = Builders<T>.Projection.Include(cols[0]);
@@ -324,7 +329,7 @@ public sealed class MongoDataBase : IDateBase
var clone = MongoHelper.Instance.Clone(entity);
using (await _mongoDataBaseLock.Lock(clone.Id))
using (await IDateBase.DataBaseLock.Lock(clone.Id))
{
await GetCollection(collection ?? clone.GetType().Name).ReplaceOneAsync(
(IClientSessionHandle) transactionSession, d => d.Id == clone.Id, clone,
@@ -343,7 +348,7 @@ public sealed class MongoDataBase : IDateBase
var clone = MongoHelper.Instance.Clone(entity);
using (await _mongoDataBaseLock.Lock(clone.Id))
using (await IDateBase.DataBaseLock.Lock(clone.Id))
{
await GetCollection(collection ?? clone.GetType().Name).ReplaceOneAsync(d => d.Id == clone.Id, clone,
new ReplaceOptions {IsUpsert = true});
@@ -361,7 +366,7 @@ public sealed class MongoDataBase : IDateBase
var clone = MongoHelper.Instance.Clone(entity);
using (await _mongoDataBaseLock.Lock(clone.Id))
using (await IDateBase.DataBaseLock.Lock(clone.Id))
{
await GetCollection(collection ?? clone.GetType().Name).ReplaceOneAsync(d => d.Id == clone.Id, clone, new ReplaceOptions {IsUpsert = true});
}
@@ -377,7 +382,7 @@ public sealed class MongoDataBase : IDateBase
var clones = MongoHelper.Instance.Clone(entities);
using (await _mongoDataBaseLock.Lock(id))
using (await IDateBase.DataBaseLock.Lock(id))
{
foreach (Entity clone in clones)
{
@@ -405,7 +410,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask InsertBatch<T>(IEnumerable<T> list, string collection = null) where T : Entity, new()
{
using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64()))
using (await IDateBase.DataBaseLock.Lock(RandomHelper.RandInt64()))
{
await GetCollection<T>(collection ?? typeof(T).Name).InsertManyAsync(list);
}
@@ -413,7 +418,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask InsertBatch<T>(object transactionSession, IEnumerable<T> list, string collection = null) where T : Entity, new()
{
using (await _mongoDataBaseLock.Lock(RandomHelper.RandInt64()))
using (await IDateBase.DataBaseLock.Lock(RandomHelper.RandInt64()))
{
await GetCollection<T>(collection ?? typeof(T).Name).InsertManyAsync((IClientSessionHandle) transactionSession, list);
}
@@ -425,7 +430,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<long> Remove<T>(object transactionSession, long id, string collection = null) where T : Entity, new()
{
using (await _mongoDataBaseLock.Lock(id))
using (await IDateBase.DataBaseLock.Lock(id))
{
var result = await GetCollection<T>(collection).DeleteOneAsync((IClientSessionHandle) transactionSession, d => d.Id == id);
return result.DeletedCount;
@@ -434,7 +439,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<long> Remove<T>(long id, string collection = null) where T : Entity, new()
{
using (await _mongoDataBaseLock.Lock(id))
using (await IDateBase.DataBaseLock.Lock(id))
{
var result = await GetCollection<T>(collection).DeleteOneAsync(d => d.Id == id);
return result.DeletedCount;
@@ -443,7 +448,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<long> Remove<T>(long id, object transactionSession, Expression<Func<T, bool>> filter, string collection = null) where T : Entity, new()
{
using (await _mongoDataBaseLock.Lock(id))
using (await IDateBase.DataBaseLock.Lock(id))
{
var result = await GetCollection<T>(collection).DeleteManyAsync((IClientSessionHandle) transactionSession, filter);
return result.DeletedCount;
@@ -452,7 +457,7 @@ public sealed class MongoDataBase : IDateBase
public async FTask<long> Remove<T>(long id, Expression<Func<T, bool>> filter, string collection = null) where T : Entity, new()
{
using (await _mongoDataBaseLock.Lock(id))
using (await IDateBase.DataBaseLock.Lock(id))
{
var result = await GetCollection<T>(collection).DeleteManyAsync(filter);
return result.DeletedCount;

View File

@@ -96,6 +96,38 @@ namespace TEngine.DataStructure
}
}
public void Read(Memory<byte> memory, int count)
{
if (count > Length)
{
throw new Exception($"bufferList length < count, {Length} {count}");
}
var copyCount = 0;
while (copyCount < count)
{
var n = count - copyCount;
var asMemory = First.AsMemory();
if (ChunkSize - FirstIndex > n)
{
var slice = asMemory.Slice(FirstIndex, n);
slice.CopyTo(memory.Slice(copyCount, n));
FirstIndex += n;
copyCount += n;
}
else
{
var length = ChunkSize - FirstIndex;
var slice = asMemory.Slice(FirstIndex, length);
slice.CopyTo(memory.Slice(copyCount, length));
copyCount += ChunkSize - FirstIndex;
FirstIndex = 0;
RemoveFirst();
}
}
}
public override int Read(byte[] buffer, int offset, int count)
{
if (buffer.Length < offset + count)
@@ -124,7 +156,6 @@ namespace TEngine.DataStructure
}
Array.Copy(First, FirstIndex, buffer, copyCount + offset, ChunkSize - FirstIndex);
copyCount += ChunkSize - FirstIndex;
FirstIndex = 0;

View File

@@ -35,9 +35,9 @@ namespace TEngine.DataStructure
if (layer <= rLevel)
{
var currentRight = cur.Right;
cur.Right = new SkipTableNode<TValue>(sortKey, viceKey, key, value,
layer == 1 ? cur.Index + 1 : 0, cur, cur.Right, null);
cur.Right = new SkipTableNode<TValue>(sortKey, viceKey, key, value, layer == 1 ? cur.Index + 1 : 0, cur, cur.Right, null);
if (currentRight != null)
{
currentRight.Left = cur.Right;

View File

@@ -79,6 +79,7 @@ public static class Define
public static readonly HashSet<string> ColTypeSet = new HashSet<string>()
{
"", "0", "bool", "byte", "short", "ushort", "int", "uint", "long", "ulong", "float", "string", "AttrConfig",
"IntDictionaryConfig", "StringDictionaryConfig",
"short[]", "int[]", "long[]", "float[]", "string[]"
};
/// <summary>

View File

@@ -13,6 +13,7 @@ using Newtonsoft.Json;
// ReSharper disable SuspiciousTypeConversion.Global
// ReSharper disable InconsistentNaming
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
namespace TEngine
{
@@ -194,14 +195,14 @@ namespace TEngine
public static T Create<T>(Scene scene, bool isRunEvent = true) where T : Entity, new()
{
var entity = Create<T>(scene.RouteId, isRunEvent);
var entity = Create<T>(scene.LocationId, isRunEvent);
entity.Scene = scene;
return entity;
}
public static T Create<T>(Scene scene, long id, bool isRunEvent = true) where T : Entity, new()
{
var entity = Create<T>(id, scene.RouteId, isRunEvent);
var entity = Create<T>(id, scene.LocationId, isRunEvent);
entity.Scene = scene;
return entity;
}
@@ -235,12 +236,12 @@ namespace TEngine
return entity;
}
private static T Create<T>(long id, uint routeId, bool isRunEvent = true) where T : Entity, new()
protected static T Create<T>(long id, uint locationId, bool isRunEvent = true) where T : Entity, new()
{
return Create<T>(id, IdFactory.NextEntityId(routeId), isRunEvent);
return Create<T>(id, IdFactory.NextEntityId(locationId), isRunEvent);
}
private static T Create<T>(long id, long runtimeId, bool isRunEvent = true) where T : Entity, new()
protected static T Create<T>(long id, long runtimeId, bool isRunEvent = true) where T : Entity, new()
{
var entity = Rent<T>(typeof(T));
entity.Id = id;
@@ -264,13 +265,6 @@ namespace TEngine
return entity;
}
protected static Scene CreateScene(long id, bool isRunEvent = true)
{
var entity = Create<Scene>(id, id, isRunEvent);
entity.Scene = entity;
return entity;
}
#endregion
#region Members
@@ -293,12 +287,12 @@ namespace TEngine
[BsonIgnore]
[JsonIgnore]
[IgnoreDataMember]
public Scene Scene { get; private set; }
public Scene Scene { get; protected set; }
[BsonIgnore]
[JsonIgnore]
[IgnoreDataMember]
public Entity Parent { get; private set; }
public Entity Parent { get; protected set; }
[BsonElement("t")]
[BsonIgnoreIfNull]
@@ -321,7 +315,7 @@ namespace TEngine
public T AddComponent<T>() where T : Entity, new()
{
var entity = Create<T>(Scene.RouteId, false);
var entity = Create<T>(Id, Scene.LocationId, false);
AddComponent(entity);
EntitiesSystem.Instance.Awake(entity);
EntitiesSystem.Instance.StartUpdate(entity);
@@ -330,7 +324,7 @@ namespace TEngine
public T AddComponent<T>(long id) where T : Entity, new()
{
var entity = Create<T>(id, Scene.RouteId, false);
var entity = Create<T>(id, Scene.LocationId, false);
AddComponent(entity);
EntitiesSystem.Instance.Awake(entity);
EntitiesSystem.Instance.StartUpdate(entity);
@@ -367,6 +361,12 @@ namespace TEngine
}
else
{
#if TENGINE_NET
if (component is ISupportedSingleCollection && component.Id != Id)
{
Log.Error($"component type :{component.GetType().FullName} for implementing ISupportedSingleCollection, it is required that the Id must be the same as the parent");
}
#endif
if (_tree == null)
{
_tree = DictionaryPool<Type, Entity>.Create();
@@ -402,9 +402,54 @@ namespace TEngine
}
#endregion
#if TENGINE_NET
#region ForEach
public IEnumerable<Entity> ForEachSingleCollection
{
get
{
foreach (var (_, treeEntity) in _tree)
{
if (treeEntity is not ISupportedSingleCollection)
{
continue;
}
yield return treeEntity;
}
}
}
public IEnumerable<Entity> ForEachTransfer
{
get
{
if (_tree != null)
{
foreach (var (_, treeEntity) in _tree)
{
if (treeEntity is ISupportedSingleCollection || treeEntity is ISupportedTransfer)
{
yield return treeEntity;
}
}
}
if (_multiDb != null)
{
foreach (var treeEntity in _multiDb)
{
if (treeEntity is not ISupportedTransfer)
{
continue;
}
yield return treeEntity;
}
}
}
}
#endregion
#endif
#region GetComponent
public T GetComponent<T>() where T : Entity, new()
{
return GetComponent(typeof(T)) as T;
@@ -528,8 +573,9 @@ namespace TEngine
try
{
Scene = scene;
#if TENGINE_NET
RuntimeId = IdFactory.NextEntityId(scene.RouteId);
RuntimeId = IdFactory.NextEntityId(scene.LocationId);
#else
RuntimeId = IdFactory.NextRunTimeId();
#endif
@@ -546,7 +592,6 @@ namespace TEngine
foreach (var entity in _treeDb)
{
entity.Parent = this;
entity.Scene = scene;
entity.Deserialize(scene, resetId);
_tree.Add(entity.GetType(), entity);
}
@@ -558,7 +603,6 @@ namespace TEngine
foreach (var entity in _multiDb)
{
entity.Parent = this;
entity.Scene = scene;
entity.Deserialize(scene, resetId);
_multi.Add(entity.Id, (ISupportedMultiEntity)entity);
}

View File

@@ -0,0 +1,30 @@
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
namespace TEngine
{
public readonly struct EntityReference<T> where T : Entity
{
private readonly T _entity;
private readonly long _runTimeId;
private EntityReference(T t)
{
_entity = t;
_runTimeId = t.RuntimeId;
}
public static implicit operator EntityReference<T>(T t)
{
return new EntityReference<T>(t);
}
public static implicit operator T(EntityReference<T> v)
{
if (v._entity == null)
{
return null;
}
return v._entity.RuntimeId != v._runTimeId ? null : v._entity;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c827c6f914b64f5d9eb0a1f29cc2c018
timeCreated: 1691083017

View File

@@ -0,0 +1,8 @@
#if TENGINE_NET
namespace TEngine;
/// <summary>
/// Entity保存到数据库的时候会根据子组件设置分离存储特性分表存储在不同的集合表中
/// </summary>
public interface ISingleCollectionRoot { }
#endif

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 14567fd01a0bc7b438d6579e38a9cfc4
guid: 3dfa22a9da7a76b41bf6511e45b22b53
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -0,0 +1,23 @@
#if TENGINE_NET
namespace TEngine;
/// <summary>
/// Entity是单一集合、保存到数据库的时候不会跟随父组件保存在一个集合里、会单独保存在一个集合里
/// 需要配合SingleCollectionAttribute一起使用、如在Entity类头部定义SingleCollectionAttribute(typeOf(Unit))
/// SingleCollectionAttribute用来定义这个Entity是属于哪个Entity的子集
/// </summary>
public interface ISupportedSingleCollection { }
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public class SingleCollectionAttribute : Attribute
{
public readonly Type RootType;
public readonly string CollectionName;
public SingleCollectionAttribute(Type rootType, string collectionName)
{
RootType = rootType;
CollectionName = collectionName;
}
}
#endif

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: eb202d4e9e36ea846861fa8378dd1c23
guid: d9654cddb77221d4eaa991df735b12c2
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -0,0 +1,8 @@
#if TENGINE_NET
namespace TEngine;
/// <summary>
/// Entity支持传送。
/// </summary>
public interface ISupportedTransfer { }
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 06770f37cdfc480fb0c270ea93a10d26
timeCreated: 1691083054

View File

@@ -9,19 +9,19 @@ using TEngine.Core.DataBase;
#pragma warning disable CS8618
namespace TEngine
{
public sealed class Scene : Entity, INotSupportedPool
public class Scene : Entity, INotSupportedPool
{
public string Name { get; private set; }
public uint RouteId { get; private set; }
public uint LocationId { get; private set; }
#if TENGINE_UNITY
public Session Session { get; private set; }
public SceneConfigInfo SceneInfo { get; private set; }
#endif
#if TENGINE_NET
public int SceneType { get; private set; }
public int SceneSubType { get; private set; }
public World World { get; private set; }
public Server Server { get; private set; }
public uint SceneConfigId { get; private set; }
public SceneConfigInfo SceneInfo => ConfigTableManage.SceneConfig(SceneConfigId);
#endif
public static readonly List<Scene> Scenes = new List<Scene>();
@@ -33,11 +33,12 @@ namespace TEngine
}
Name = null;
RouteId = 0;
this.LocationId = 0;
#if TENGINE_NET
World = null;
Server = null;
SceneConfigId = 0;
SceneType = 0;
SceneSubType = 0;
#endif
#if TENGINE_UNITY
SceneInfo = null;
@@ -59,14 +60,27 @@ namespace TEngine
}
}
#if TENGINE_UNITY
public static Scene Create()
{
var sceneId = IdFactory.NextRunTimeId();
var scene = Create<Scene>(sceneId, sceneId);
scene.Scene = scene;
scene.Parent = scene;
Scenes.Add(scene);
return scene;
}
public static Scene Create(string name)
{
var runTimeId = IdFactory.NextRunTimeId();
var scene = CreateScene(runTimeId);
var sceneId = IdFactory.NextRunTimeId();
var scene = Create<Scene>(sceneId, sceneId);
scene.Scene = scene;
scene.Parent = scene;
scene.Name = name;
Scenes.Add(scene);
return scene;
}
public void CreateSession(string remoteAddress, NetworkProtocolType networkProtocolType, Action onConnectComplete, Action onConnectFail,Action onConnectDisconnect, int connectTimeout = 5000)
{
var address = NetworkHelper.ToIPEndPoint(remoteAddress);
@@ -77,84 +91,96 @@ namespace TEngine
}
#else
/// <summary>
/// 创建一个Scene
/// 创建一个Scene
/// </summary>
/// <param name="scene"></param>
/// <param name="sceneType"></param>
/// <param name="sceneSubType"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static async FTask<T> Create<T>(Scene scene, int sceneType, int sceneSubType) where T : Scene, new()
{
var newScene = Create<T>(scene);
newScene.Scene = newScene;
newScene.Parent = scene;
newScene.SceneType = sceneType;
newScene.SceneSubType = sceneSubType;
newScene.Server = scene.Server;
newScene.LocationId = scene.Server.Id;
if (scene.World != null)
{
newScene.World = scene.World;
}
if (sceneType > 0)
{
await EventSystem.Instance.PublishAsync(new OnCreateScene(newScene));
}
Scenes.Add(newScene);
return newScene;
}
/// <summary>
/// 创建一个Scene。
/// </summary>
/// <param name="server"></param>
/// <param name="sceneType"></param>
/// <param name="sceneSubType"></param>
/// <param name="sceneId"></param>
/// <param name="worldId"></param>
/// <param name="networkProtocol"></param>
/// <param name="outerBindIp"></param>
/// <param name="sceneInfo"></param>
/// <param name="runEvent"></param>
/// <param name="onSetNetworkComplete"></param>
/// <param name="outerPort"></param>
/// <returns></returns>
public static async FTask<Scene> Create(Server server, string outerBindIp, SceneConfigInfo sceneInfo, Action<Session> onSetNetworkComplete = null, bool runEvent = true)
public static async FTask<Scene> Create(Server server, int sceneType = 0, int sceneSubType = 0, long sceneId = 0, uint worldId = 0, string networkProtocol = null, string outerBindIp = null, int outerPort = 0)
{
var scene = CreateScene(sceneInfo.EntityId);
sceneInfo.Scene = scene;
scene.Name = sceneInfo.Name;
scene.RouteId = sceneInfo.RouteId;
if (sceneId == 0)
{
sceneId = new EntityIdStruct(server.Id, 0, 0);
}
var scene = Create<Scene>(sceneId, sceneId);
scene.Scene = scene;
scene.Parent = scene;
scene.SceneType = sceneType;
scene.SceneSubType = sceneSubType;
scene.Server = server;
scene.SceneConfigId = sceneInfo.Id;
scene.LocationId = server.Id;
if (sceneInfo.WorldId != 0)
if (worldId != 0)
{
// 有可能不需要数据库、所以这里默认0的情况下就不创建数据库了
scene.World = World.Create(sceneInfo.WorldId);
scene.World = World.Create(worldId);
}
if (!string.IsNullOrEmpty(sceneInfo.NetworkProtocol) && !string.IsNullOrEmpty(outerBindIp) && sceneInfo.OuterPort != 0)
if (!string.IsNullOrEmpty(networkProtocol) && !string.IsNullOrEmpty(outerBindIp) && outerPort != 0)
{
// 设置Scene的网络、目前只支持KCP和TCP
var networkProtocolType = Enum.Parse<NetworkProtocolType>(sceneInfo.NetworkProtocol);
var networkProtocolType = Enum.Parse<NetworkProtocolType>(networkProtocol);
var serverNetworkComponent = scene.AddComponent<ServerNetworkComponent>();
var address = NetworkHelper.ToIPEndPoint($"{outerBindIp}:{sceneInfo.OuterPort}");
var address = NetworkHelper.ToIPEndPoint($"{outerBindIp}:{outerPort}");
serverNetworkComponent.Initialize(networkProtocolType, NetworkTarget.Outer, address);
}
if (runEvent && sceneInfo.SceneType != null)
if (sceneType > 0)
{
// 没有SceneType目前只有代码创建的Scene才会这样、目前只有Server的Scene是这样
await EventSystem.Instance.PublishAsync(new OnCreateScene(sceneInfo, onSetNetworkComplete));
await EventSystem.Instance.PublishAsync(new OnCreateScene(scene));
}
Scenes.Add(scene);
return scene;
}
/// <summary>
/// 一般用于创建临时Scene、如果不是必要不建议使用这个接口
/// </summary>
/// <param name="name"></param>
/// <param name="server"></param>
/// <param name="entityId"></param>
/// <param name="sceneConfigId"></param>
/// <param name="networkProtocol"></param>
/// <param name="outerBindIp"></param>
/// <param name="outerPort"></param>
/// <param name="runEvent"></param>
/// <param name="onSetNetworkComplete"></param>
/// <returns></returns>
public static async FTask<Scene> Create(string name, Server server, long entityId, uint sceneConfigId = 0, string networkProtocol = null, string outerBindIp = null, int outerPort = 0, Action<Session> onSetNetworkComplete = null, bool runEvent = true)
{
var sceneInfo = new SceneConfigInfo()
{
Name = name,
EntityId = entityId,
Id = sceneConfigId,
NetworkProtocol = networkProtocol,
OuterPort = outerPort,
WorldId = ((EntityIdStruct)entityId).WordId
};
return await Create(server, outerBindIp, sceneInfo, onSetNetworkComplete, runEvent);
}
public static List<SceneConfigInfo> GetSceneInfoByRouteId(uint routeId)
public static List<SceneConfigInfo> GetSceneInfoByServerConfigId(uint serverConfigId)
{
var list = new List<SceneConfigInfo>();
var allSceneConfig = ConfigTableManage.AllSceneConfig();
foreach (var sceneConfigInfo in allSceneConfig)
{
if (sceneConfigInfo.RouteId != routeId)
if (sceneConfigInfo.ServerConfigId != serverConfigId)
{
continue;
}

View File

@@ -5,13 +5,11 @@ namespace TEngine
{
public struct OnCreateScene
{
public readonly SceneConfigInfo SceneInfo;
public readonly Action<Session> OnSetNetworkComplete;
public readonly Scene Scene;
public OnCreateScene(SceneConfigInfo sceneInfo, Action<Session> onSetNetworkComplete)
public OnCreateScene(Scene scene)
{
SceneInfo = sceneInfo;
OnSetNetworkComplete = onSetNetworkComplete;
Scene = scene;
}
}
}

View File

@@ -0,0 +1,89 @@
#if TENGINE_NET
using System.Text;
using TEngine.Core;
using TEngine.Helper;
namespace TEngine.CustomExport;
public sealed class SceneTypeConfigToEnum : ACustomExport
{
public override void Run()
{
var fullPath = FileHelper.GetFullPath("../../../Config/Excel/Server/SceneConfig.xlsx");
using var excelPackage = ExcelHelper.LoadExcel(fullPath);
var sceneType = new Dictionary<string, string>();
var sceneSubType = new Dictionary<string, string>();
var sceneTypeConfig = excelPackage.Workbook.Worksheets["SceneTypeConfig"];
for (var row = 3; row <= sceneTypeConfig.Dimension.Rows; row++)
{
var sceneTypeId = sceneTypeConfig.GetCellValue(row, 1);
var sceneTypeStr = sceneTypeConfig.GetCellValue(row, 2);
if (string.IsNullOrEmpty(sceneTypeId) || string.IsNullOrEmpty(sceneTypeStr))
{
continue;
}
sceneType.Add(sceneTypeId, sceneTypeStr);
}
var sceneSubTypeConfig = excelPackage.Workbook.Worksheets["SceneSubTypeConfig"];
for (var row = 3; row <= sceneSubTypeConfig.Dimension.Rows; row++)
{
var sceneSubTypeId = sceneSubTypeConfig.GetCellValue(row, 1);
var sceneSubTypeStr = sceneSubTypeConfig.GetCellValue(row, 2);
if (string.IsNullOrEmpty(sceneSubTypeId) || string.IsNullOrEmpty(sceneSubTypeStr))
{
continue;
}
sceneSubType.Add(sceneSubTypeId, sceneSubTypeStr);
}
if (sceneType.Count > 0 || sceneSubType.Count > 0)
{
Write(CustomExportType.Server, sceneType, sceneSubType);
}
}
private void Write(CustomExportType customExportType, Dictionary<string, string> sceneTypes, Dictionary<string, string> sceneSubType)
{
var strBuilder = new StringBuilder();
var dicBuilder = new StringBuilder();
strBuilder.AppendLine("namespace TEngine\n{");
strBuilder.AppendLine("\t// 生成器自动生成,请不要手动编辑。");
strBuilder.AppendLine("\tpublic static class SceneType\n\t{");
dicBuilder.AppendLine("\n\t\tpublic static readonly Dictionary<string, int> SceneTypeDic = new Dictionary<string, int>()\n\t\t{");
foreach (var (sceneTypeId, sceneTypeStr) in sceneTypes)
{
dicBuilder.AppendLine($"\t\t\t{{ \"{sceneTypeStr}\", {sceneTypeId} }},");
strBuilder.AppendLine($"\t\tpublic const int {sceneTypeStr} = {sceneTypeId};");
}
dicBuilder.AppendLine("\t\t};");
strBuilder.Append(dicBuilder);
strBuilder.AppendLine("\t}\n");
strBuilder.AppendLine("\t// 生成器自动生成,请不要手动编辑。");
strBuilder.AppendLine("\tpublic static class SceneSubType\n\t{");
dicBuilder.Clear();
dicBuilder.AppendLine("\n\t\tpublic static readonly Dictionary<string, int> SceneSubTypeDic = new Dictionary<string, int>()\n\t\t{");
foreach (var (sceneSubTypeId, sceneSubTypeStr) in sceneSubType)
{
dicBuilder.AppendLine($"\t\t\t{{ \"{sceneSubTypeStr}\", {sceneSubTypeId} }},");
strBuilder.AppendLine($"\t\tpublic const int {sceneSubTypeStr} = {sceneSubTypeId};");
}
dicBuilder.AppendLine("\t\t};");
strBuilder.Append(dicBuilder);
strBuilder.AppendLine("\t}\n}");
Write("SceneType.cs", strBuilder.ToString(), customExportType);
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d254c1aefb624046ab545bd8cebbe57c
timeCreated: 1690561569

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2da9efac32374cb1a88c500e7ed43344
timeCreated: 1691084274

View File

@@ -0,0 +1,32 @@
using System.Collections.Generic;
using ProtoBuf;
namespace TEngine.Core
{
[ProtoContract]
public class IntDictionaryConfig
{
[ProtoMember(1, IsRequired = true)]
public Dictionary<int, int> Dic;
public int this[int key] => GetValue(key);
public bool TryGetValue(int key, out int value)
{
value = default;
if (!Dic.ContainsKey(key))
{
return false;
}
value = Dic[key];
return true;
}
private int GetValue(int key)
{
return Dic.TryGetValue(key, out var value) ? value : default;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 34b41344fd6e462bae371207cdf8a5cd
timeCreated: 1691084286

View File

@@ -0,0 +1,32 @@
using System.Collections.Generic;
using ProtoBuf;
namespace TEngine.Core
{
[ProtoContract]
public sealed class StringDictionaryConfig
{
[ProtoMember(1, IsRequired = true)]
public Dictionary<int, string> Dic;
public string this[int key] => GetValue(key);
public bool TryGetValue(int key, out string value)
{
value = default;
if (!Dic.ContainsKey(key))
{
return false;
}
value = Dic[key];
return true;
}
private string GetValue(int key)
{
return Dic.TryGetValue(key, out var value) ? value : default;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a26ad73832de4b3e986ddb58426bcced
timeCreated: 1691084294

View File

@@ -4,17 +4,12 @@ using System.Reflection;
using System.Runtime.Loader;
using System.Text;
using System.Text.RegularExpressions;
using TEngine.CustomExport;
using TEngine.DataStructure;
using TEngine.Core;
using Newtonsoft.Json;
using OfficeOpenXml;
using TEngine.Helper;
using static System.String;
#pragma warning disable CS8625
#pragma warning disable CS8604
#pragma warning disable CS8602
#pragma warning disable CS8601
#pragma warning disable CS8600
#pragma warning disable CS8618
namespace TEngine.Core;
@@ -28,10 +23,14 @@ public sealed class ExcelExporter
private readonly OneToManyList<string, ExportInfo> _tables = new OneToManyList<string, ExportInfo>();
private readonly ConcurrentDictionary<string, ExcelTable> _excelTables = new ConcurrentDictionary<string, ExcelTable>();
private readonly ConcurrentDictionary<string, ExcelWorksheet> _worksheets = new ConcurrentDictionary<string, ExcelWorksheet>();
static ExcelExporter()
{
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
}
public ExcelExporter(ExportType exportType)
{
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
var versionFilePath = Define.ExcelVersionFile;
switch (exportType)
@@ -86,6 +85,9 @@ public sealed class ExcelExporter
task.Add(Task.Run(customExport.Run));
}
}
// 添加生成SceneType的自定义导出
task.Add(Task.Run(new SceneTypeConfigToEnum().Run));
Task.WaitAll(task.ToArray());
}
@@ -226,7 +228,7 @@ public sealed class ExcelExporter
{
// 列名字第一个字符是#不参与导出
var colName = GetCellValue(worksheet, 5, col);
var colName = worksheet.GetCellValue(5, col);
if (colName.StartsWith("#", StringComparison.Ordinal))
{
continue;
@@ -234,14 +236,14 @@ public sealed class ExcelExporter
// 数值列不参与导出
var numericalCol = GetCellValue(worksheet, 3, col);
var numericalCol = worksheet.GetCellValue(3, col);
if (numericalCol != "" && numericalCol != "0")
{
continue;
}
var serverType = GetCellValue(worksheet, 1, col);
var clientType = GetCellValue(worksheet, 2, col);
var serverType = worksheet.GetCellValue(1, col);
var clientType = worksheet.GetCellValue(2, col);
var isExportServer = !IsNullOrEmpty(serverType) && serverType != "0";
var isExportClient = !IsNullOrEmpty(clientType) && clientType != "0";
@@ -350,7 +352,7 @@ public sealed class ExcelExporter
foreach (var colIndex in cols)
{
var colName = GetCellValue(excelWorksheet, 5, colIndex);
var colName = excelWorksheet.GetCellValue(5, colIndex);
if (colNameSet.Contains(colName))
{
@@ -363,19 +365,19 @@ public sealed class ExcelExporter
if (isServer)
{
colType = GetCellValue(excelWorksheet, 1, colIndex);
colType = excelWorksheet.GetCellValue(1, colIndex);
if (IsNullOrEmpty(colType) || colType == "0")
{
colType = GetCellValue(excelWorksheet, 2, colIndex);
colType = excelWorksheet.GetCellValue(2, colIndex);
}
}
else
{
colType = GetCellValue(excelWorksheet, 2, colIndex);
colType = excelWorksheet.GetCellValue(2, colIndex);
}
var remarks = GetCellValue(excelWorksheet, 4, colIndex);
var remarks = excelWorksheet.GetCellValue(4, colIndex);
fileBuilder.Append($"\n\t\t[ProtoMember({++index}, IsRequired = true)]\n");
fileBuilder.Append(
@@ -436,12 +438,12 @@ public sealed class ExcelExporter
for (var row = 7; row <= rows; row++)
{
if (GetCellValue(excelWorksheet, row, 1).StartsWith("#", StringComparison.Ordinal))
if (excelWorksheet.GetCellValue(row, 1).StartsWith("#", StringComparison.Ordinal))
{
continue;
}
var id = GetCellValue(excelWorksheet, row, 3);
var id = excelWorksheet.GetCellValue(row, 3);
if (idCheck.Contains(id))
{
@@ -525,21 +527,21 @@ public sealed class ExcelExporter
{
string colType;
var colIndex = cols[i];
var colName = GetCellValue(excelWorksheet, 5, colIndex);
var value = GetCellValue(excelWorksheet, row, colIndex);
var colName = excelWorksheet.GetCellValue(5, colIndex);
var value = excelWorksheet.GetCellValue(row, colIndex);
if (isServer)
{
colType = GetCellValue(excelWorksheet, 1, colIndex);
colType = excelWorksheet.GetCellValue(1, colIndex);
if (IsNullOrEmpty(colType) || colType == "0")
{
colType = GetCellValue(excelWorksheet, 2, colIndex);
colType = excelWorksheet.GetCellValue(2, colIndex);
}
}
else
{
colType = GetCellValue(excelWorksheet, 2, colIndex);
colType = excelWorksheet.GetCellValue(2, colIndex);
}
try
@@ -566,7 +568,7 @@ public sealed class ExcelExporter
dynamicInfo.Json.AppendLine($"{json},");
}
}
public ExcelWorksheet LoadExcel(string name, bool isAddToDic)
{
if (_worksheets.TryGetValue(name, out var worksheet))
@@ -574,7 +576,7 @@ public sealed class ExcelExporter
return worksheet;
}
worksheet = new ExcelPackage(name).Workbook.Worksheets[0];
worksheet = ExcelHelper.LoadExcel(name).Workbook.Worksheets[0];
if (isAddToDic)
{
@@ -585,26 +587,6 @@ public sealed class ExcelExporter
return worksheet;
}
private string GetCellValue(ExcelWorksheet sheet, int row, int column)
{
var cell = sheet.Cells[row, column];
try
{
if (cell.Value == null)
{
return "";
}
var s = cell.GetValue<string>();
return s.Trim();
}
catch (Exception e)
{
throw new Exception($"Rows {row} Columns {column} Content {cell.Text} {e}");
}
}
private void SetNewValue(PropertyInfo propertyInfo, AProto config, string type, string value)
{
if (IsNullOrWhiteSpace(value))
@@ -753,20 +735,34 @@ public sealed class ExcelExporter
return;
}
// case "AttrConfig":
// {
// if (value.Trim() == "" || value.Trim() == "{}")
// {
// propertyInfo.SetValue(config, null);
// return;
// }
//
// var attr = new AttrConfig {KV = JsonConvert.DeserializeObject<Dictionary<int, int>>(value)};
//
// propertyInfo.SetValue(config, attr);
//
// return;
// }
case "IntDictionaryConfig":
{
if (value.Trim() == "" || value.Trim() == "{}")
{
propertyInfo.SetValue(config, null);
return;
}
var attr = new IntDictionaryConfig {Dic = JsonConvert.DeserializeObject<Dictionary<int, int>>(value)};
propertyInfo.SetValue(config, attr);
return;
}
case "StringDictionaryConfig":
{
if (value.Trim() == "" || value.Trim() == "{}")
{
propertyInfo.SetValue(config, null);
return;
}
var attr = new StringDictionaryConfig {Dic = JsonConvert.DeserializeObject<Dictionary<int, string>>(value)};
propertyInfo.SetValue(config, attr);
return;
}
default:
throw new NotSupportedException($"不支持此类型: {type}");
}

View File

@@ -33,7 +33,7 @@ namespace TEngine.Core
{
var configFile = GetConfigPath(dataConfig);
var bytes = File.ReadAllBytes(configFile);
var data = (AProto) ProtoBufHelper.FromBytes(typeof(T), bytes, 0, bytes.Length);
var data = (T)ProtoBufHelper.FromBytes(typeof(T), bytes, 0, bytes.Length);
data.AfterDeserialization();
ConfigDic[dataConfig] = data;
return (T)data;
@@ -65,7 +65,7 @@ namespace TEngine.Core
{
var configFile = GetConfigPath(type.Name);
var bytes = File.ReadAllBytes(configFile);
var data = (AProto) ProtoBufHelper.FromBytes(type, bytes, 0, bytes.Length);
var data = (AProto)ProtoBufHelper.FromBytes(type, bytes, 0, bytes.Length);
data.AfterDeserialization();
ConfigDic[dataConfig] = data;
return data;

View File

@@ -25,8 +25,8 @@ public sealed class Exporter
LogInfo("请输入你想要做的操作:");
LogInfo("1:导出网络协议ProtoBuf");
LogInfo("2:增量导出Excel包含常量枚举");
LogInfo("3:全量导出Excel包含常量枚举");
LogInfo("2:增量导出服务器启动Excel包含常量枚举");
LogInfo("3:全量导出服务器启动Excel包含常量枚举");
var keyChar = Console.ReadKey().KeyChar;

View File

@@ -424,7 +424,9 @@ public sealed class ProtoBufExporter
"int32[]" => "int[] { }",
"int64[]" => "long[] { }",
"int32" => "int",
"uint32" => "uint",
"int64" => "long",
"uint64" => "ulong",
_ => type
};
}

View File

@@ -0,0 +1,34 @@
#if TENGINE_NET
using OfficeOpenXml;
namespace TEngine.Helper;
public static class ExcelHelper
{
public static ExcelPackage LoadExcel(string name)
{
return new ExcelPackage(name);
}
public static string GetCellValue(this ExcelWorksheet sheet, int row, int column)
{
ExcelRange cell = sheet.Cells[row, column];
try
{
if (cell.Value == null)
{
return "";
}
string s = cell.GetValue<string>();
return s.Trim();
}
catch (Exception e)
{
throw new Exception($"Rows {row} Columns {column} Content {cell.Text} {e}");
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2ea3356f30cb40949949691a0e258a05
timeCreated: 1690561972

View File

@@ -5,6 +5,16 @@ namespace TEngine.Core
{
public static 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>

View File

@@ -4,7 +4,6 @@ using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Conventions;
using TrueSync;
using Unity.Mathematics;
using MongoHelper = TEngine.Core.MongoHelper;
@@ -23,12 +22,6 @@ public sealed class MongoHelper : Singleton<MongoHelper>
RegisterStruct<float3>();
RegisterStruct<float4>();
RegisterStruct<quaternion>();
RegisterStruct<FP>();
RegisterStruct<TSVector>();
RegisterStruct<TSVector2>();
RegisterStruct<TSVector4>();
RegisterStruct<TSQuaternion>();
}
public static void RegisterStruct<T>() where T : struct
@@ -95,11 +88,54 @@ public sealed class MongoHelper : Singleton<MongoHelper>
{
return BsonSerializer.Deserialize(str, type);
}
public object Deserialize(Span<byte> span, Type type)
{
using var stream = MemoryStreamHelper.GetRecyclableMemoryStream();
stream.Write(span);
stream.Seek(0, SeekOrigin.Begin);
return BsonSerializer.Deserialize(stream, type);
}
public object Deserialize(Memory<byte> memory, Type type)
{
using var stream = MemoryStreamHelper.GetRecyclableMemoryStream();
stream.Write(memory.Span);
stream.Seek(0, SeekOrigin.Begin);
return BsonSerializer.Deserialize(stream, type);
}
public object Deserialize<T>(Span<byte> span)
{
using var stream = MemoryStreamHelper.GetRecyclableMemoryStream();
stream.Write(span);
stream.Seek(0, SeekOrigin.Begin);
return BsonSerializer.Deserialize<T>(stream);
}
public object Deserialize<T>(Memory<byte> memory)
{
using var stream = MemoryStreamHelper.GetRecyclableMemoryStream();
stream.Seek(0, SeekOrigin.Begin);
return BsonSerializer.Deserialize<T>(stream);
}
public T Deserialize<T>(byte[] bytes)
{
return BsonSerializer.Deserialize<T>(bytes);
}
public void SerializeTo<T>(T t, Memory<byte> memory)
{
using var memoryStream = new MemoryStream();
using (var writer = new BsonBinaryWriter(memoryStream, BsonBinaryWriterSettings.Defaults))
{
BsonSerializer.Serialize(writer, typeof(T), t);
}
memoryStream.GetBuffer().AsMemory().CopyTo(memory);
}
public object Deserialize(byte[] bytes, Type type)
{
@@ -143,9 +179,8 @@ public sealed class MongoHelper : Singleton<MongoHelper>
public void SerializeTo<T>(T t, MemoryStream stream)
{
var bytes = t.ToBson();
stream.Write(bytes, 0, bytes.Length);
using var writer = new BsonBinaryWriter(stream, BsonBinaryWriterSettings.Defaults);
BsonSerializer.Serialize(writer, typeof(T), t);
}
public T Clone<T>(T t)

View File

@@ -1,38 +1,89 @@
using System;
using System.Buffers;
using System.IO;
using System.Reflection;
using ProtoBuf;
#pragma warning disable CS8604
using ProtoBuf.Meta;
namespace TEngine.Core
{
public static class ProtoBufHelper
{
public static object FromSpan(Type type, Span<byte> span)
{
#if TENGINE_UNITY
using var recyclableMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream();
recyclableMemoryStream.Write(span);
recyclableMemoryStream.Seek(0, SeekOrigin.Begin);
return Serializer.Deserialize(type, recyclableMemoryStream);
#else
return RuntimeTypeModel.Default.Deserialize(type, span);
#endif
}
public static object FromMemory(Type type, Memory<byte> memory)
{
#if TENGINE_UNITY
using var recyclableMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream();
recyclableMemoryStream.Write(memory.Span);
recyclableMemoryStream.Seek(0, SeekOrigin.Begin);
return Serializer.Deserialize(type, recyclableMemoryStream);
#else
return RuntimeTypeModel.Default.Deserialize(type, memory);
#endif
}
public static object FromBytes(Type type, byte[] bytes, int index, int count)
{
using var stream = new MemoryStream(bytes, index, count);
#if TENGINE_UNITY
using var stream = MemoryStreamHelper.GetRecyclableMemoryStream();
stream.Write(bytes, index, count);
stream.Seek(0, SeekOrigin.Begin);
return Serializer.Deserialize(type, stream);
#else
var memory = new Memory<byte>(bytes, index, count);
return RuntimeTypeModel.Default.Deserialize(type, memory);
#endif
}
public static T FromBytes<T>(byte[] bytes)
{
using var stream = new MemoryStream(bytes, 0, bytes.Length);
#if TENGINE_UNITY
using var stream = MemoryStreamHelper.GetRecyclableMemoryStream();
stream.Write(bytes, 0, bytes.Length);
stream.Seek(0, SeekOrigin.Begin);
return Serializer.Deserialize<T>(stream);
// return FromBytes<T>(bytes, 0, bytes.Length);
#else
return Serializer.Deserialize<T>(new Span<byte>(bytes));
#endif
}
public static T FromBytes<T>(byte[] bytes, int index, int count)
{
using var stream = new MemoryStream(bytes, index, count);
#if TENGINE_UNITY
using var stream = MemoryStreamHelper.GetRecyclableMemoryStream();
stream.Write(bytes, 0, bytes.Length);
stream.Seek(0, SeekOrigin.Begin);
return Serializer.Deserialize<T>(stream);
#else
return Serializer.Deserialize<T>(new Span<byte>(bytes, index, count));
#endif
}
public static byte[] ToBytes(object message)
{
using var stream = new MemoryStream();
using var stream = MemoryStreamHelper.GetRecyclableMemoryStream();
Serializer.Serialize(stream, message);
return stream.ToArray();
}
public static void ToMemory(object message, Memory<byte> memory)
{
using var stream = MemoryStreamHelper.GetRecyclableMemoryStream();
Serializer.Serialize(stream, message);
stream.GetBuffer().AsMemory().CopyTo(memory);
}
public static void ToStream(object message, MemoryStream stream)
{
Serializer.Serialize(stream, message);
@@ -50,8 +101,8 @@ namespace TEngine.Core
public static T Clone<T>(T t)
{
var bytes = ToBytes(t);
using var stream = new MemoryStream(bytes, 0, bytes.Length);
using var stream = MemoryStreamHelper.GetRecyclableMemoryStream();
Serializer.Serialize(stream, t);
return Serializer.Deserialize<T>(stream);
}
}

View File

@@ -14,26 +14,26 @@ namespace TEngine.Core
public uint Time{ get; private set; }
public uint Sequence{ get; private set; }
public uint RouteId { get; private set; }
public uint LocationId { get; private set; }
public ushort AppId => (ushort)(RouteId >> 10 & RouteIdStruct.MaskAppId);
public ushort WordId=> (ushort)(RouteId & RouteIdStruct.MaskWordId);
public ushort AppId => (ushort)(this.LocationId >> 10 & RouteIdStruct.MaskAppId);
public ushort WordId=> (ushort)(this.LocationId & RouteIdStruct.MaskWordId);
public const int MaskRouteId = 0x3FFFF;
public const int MaskSequence = 0xFFFF;
public EntityIdStruct(uint routeId, uint time, uint sequence)
public EntityIdStruct(uint locationId, uint time, uint sequence)
{
Time = time;
Sequence = sequence;
RouteId = routeId;
LocationId = locationId;
}
public static implicit operator long(EntityIdStruct entityIdStruct)
{
ulong result = 0;
result |= entityIdStruct.Sequence;
result |= (ulong)entityIdStruct.RouteId << 16;
result |= (ulong)entityIdStruct.LocationId << 16;
result |= (ulong)entityIdStruct.Time << 34;
return (long)result;
}
@@ -46,7 +46,7 @@ namespace TEngine.Core
Sequence = (uint) (result & MaskSequence)
};
result >>= 16;
idStruct.RouteId = (uint) (result & 0x3FFFF);
idStruct.LocationId = (uint) (result & 0x3FFFF);
result >>= 18;
idStruct.Time = (uint) result;
return idStruct;

View File

@@ -32,7 +32,7 @@ namespace TEngine.Core
return new RuntimeIdStruct(_lastRunTimeIdTime, _lastRunTimeIdSequence);
}
public static long NextEntityId(uint routeId)
public static long NextEntityId(uint locationId)
{
var time = (uint)((TimeHelper.Now - Epoch2023) / 1000);
@@ -47,7 +47,7 @@ namespace TEngine.Core
_lastEntityIdSequence = 0;
}
return new EntityIdStruct(routeId, _lastEntityIdTime, _lastEntityIdSequence);
return new EntityIdStruct(locationId, _lastEntityIdTime, _lastEntityIdSequence);
}
public static uint GetRouteId(long entityId)

View File

@@ -217,7 +217,7 @@ namespace TEngine
StringBuilder infoBuilder = GetFormatString(type, logString);
if (type == LogLevel.ERROR || type == LogLevel.WARNING || type == LogLevel.EXCEPTION)
if (type == LogLevel.ERROR || type == LogLevel.EXCEPTION)
{
StackFrame[] sf = new StackTrace().GetFrames();
for (int i = 0; i < sf.Length; i++)

View File

@@ -26,6 +26,7 @@ namespace TEngine.Logic
{
_session = (Session)Parent;
_selfRunTimeId = RuntimeId;
RepeatedSend().Coroutine();
_timerId = TimerScheduler.Instance.Unity.RepeatedTimer(interval, () => RepeatedSend().Coroutine());
}
@@ -57,6 +58,7 @@ namespace TEngine.Logic
var responseTime = TimeHelper.Now;
Ping = (int)(responseTime - requestTime) / 2;
TimeHelper.TimeDiff = pingResponse.Now + Ping - responseTime;
Log.Info("----------- HeartBeat -----------");
}
}
}

View File

@@ -51,7 +51,7 @@ public class SessionIdleCheckerComponent: Entity
return;
}
Log.Warning($"session timeout id:{Id}");
Log.Warning($"session timeout id:{Id} timeNow:{timeNow} _session.LastReceiveTime:{_session.LastReceiveTime} _timeOut:{_timeOut}");
_session.Dispose();
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.IO;
using TEngine.Core;
using System.Buffers;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
#pragma warning disable CS8625
#pragma warning disable CS8618

View File

@@ -2,16 +2,16 @@ namespace TEngine
{
public class SceneConfigInfo
{
public Scene Scene;
public long EntityId;
public uint Id;
public string SceneType;
public string Name;
public string NetworkProtocol;
public uint RouteId;
public long EntityId;
public int SceneType;
public int SceneSubType;
public string SceneTypeStr;
public string SceneSubTypeStr;
public uint ServerConfigId;
public uint WorldId;
public int OuterPort;
public string NetworkProtocol;
}
}

View File

@@ -103,13 +103,13 @@ namespace TEngine
private static readonly Dictionary<uint, Server> Servers = new Dictionary<uint, Server>();
public static async FTask Create(uint routeId)
public static async FTask Create(uint serverConfigId)
{
var serverConfigInfo = ConfigTableManage.ServerConfig(routeId);
var serverConfigInfo = ConfigTableManage.ServerConfig(serverConfigId);
if (serverConfigInfo == null)
{
Log.Error($"not found server by Id:{routeId}");
Log.Error($"not found server by Id:{serverConfigId}");
return;
}
@@ -121,14 +121,14 @@ namespace TEngine
return;
}
var sceneInfos = Scene.GetSceneInfoByRouteId(routeId);
await Create(routeId, machineConfigInfo.InnerBindIP, serverConfigInfo.InnerPort, machineConfigInfo.OuterBindIP, sceneInfos);
// Log.Info($"ServerId:{routeId} is start complete");
var sceneInfos = Scene.GetSceneInfoByServerConfigId(serverConfigId);
await Create(serverConfigId, machineConfigInfo.InnerBindIP, serverConfigInfo.InnerPort, machineConfigInfo.OuterBindIP, sceneInfos);
// Log.Info($"ServerId:{serverConfigId} is start complete");
}
public static async FTask<Server> Create(uint routeId, string innerBindIp, int innerPort, string outerBindIp, List<SceneConfigInfo> sceneInfos)
public static async FTask<Server> Create(uint serverConfigId, string innerBindIp, int innerPort, string outerBindIp, List<SceneConfigInfo> sceneInfos)
{
if (Servers.TryGetValue(routeId, out var server))
if (Servers.TryGetValue(serverConfigId, out var server))
{
return server;
}
@@ -137,10 +137,10 @@ namespace TEngine
server = new Server
{
Id = routeId
Id = serverConfigId
};
server.Scene = await Scene.Create($"ServerScene{routeId}", server, new EntityIdStruct(routeId, 0, 0));
server.Scene = await Scene.Create(server);
// 创建网络、Server下的网络只能是内部网络、外部网络是在Scene中定义
@@ -155,16 +155,17 @@ namespace TEngine
foreach (var sceneConfig in sceneInfos)
{
await Scene.Create(server, outerBindIp, sceneConfig);
await Scene.Create(server, sceneConfig.SceneType, sceneConfig.SceneSubType, sceneConfig.EntityId,
sceneConfig.WorldId, sceneConfig.NetworkProtocol, outerBindIp, sceneConfig.OuterPort);
}
Servers.Add(routeId, server);
Servers.Add(serverConfigId, server);
return server;
}
public static Server Get(uint routeId)
public static Server Get(uint serverConfigId)
{
return Servers.TryGetValue(routeId, out var server) ? server : null;
return Servers.TryGetValue(serverConfigId, out var server) ? server : null;
}
#endregion

View File

@@ -9,7 +9,6 @@ namespace TEngine.Core.Network
{
private AClientNetwork Network { get; set; }
public Session Session { get; private set; }
private Action _onConnectDisconnect;
public void Initialize(NetworkProtocolType networkProtocolType, NetworkTarget networkTarget)
{
@@ -39,7 +38,6 @@ namespace TEngine.Core.Network
throw new NotSupportedException("Network is null or isDisposed");
}
_onConnectDisconnect = onConnectDisconnect;
Network.Connect(remoteEndPoint, onConnectComplete, onConnectFail, onConnectDisconnect, connectTimeout);
Session = Session.Create(Network);
}
@@ -53,7 +51,6 @@ namespace TEngine.Core.Network
}
Session = null;
_onConnectDisconnect?.Invoke();
base.Dispose();
}
}

View File

@@ -1,5 +1,3 @@
using TEngine.Core;
#if TENGINE_NET
namespace TEngine.Core.Network;
@@ -11,14 +9,8 @@ public sealed class ServerInnerSession : Session
{
return;
}
// 序列化消息到流中
var memoryStream = MemoryStreamHelper.GetRecyclableMemoryStream();
InnerPacketParser.Serialize(message, memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
// 分发消息
var packInfo = InnerPackInfo.Create(rpcId, routeId, ((IMessage)message).OpCode(), 0, memoryStream);
NetworkMessageScheduler.Scheduler(this, packInfo).Coroutine();
NetworkMessageScheduler.InnerScheduler(this, rpcId, routeId, ((IMessage)message).OpCode(), 0, message).Coroutine();
}
public override void Send(IRouteMessage routeMessage, uint rpcId = 0, long routeId = 0)
@@ -27,13 +19,8 @@ public sealed class ServerInnerSession : Session
{
return;
}
// 序列化消息到流中
var memoryStream = MemoryStreamHelper.GetRecyclableMemoryStream();
InnerPacketParser.Serialize(routeMessage, memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
// 分发消息
var packInfo = InnerPackInfo.Create(rpcId, routeId, routeMessage.OpCode(), routeMessage.RouteTypeOpCode(), memoryStream);
NetworkMessageScheduler.Scheduler(this, packInfo).Coroutine();
NetworkMessageScheduler.InnerScheduler(this, rpcId, routeId, routeMessage.OpCode(), routeMessage.RouteTypeOpCode(), routeMessage).Coroutine();
}
public override void Send(MemoryStream memoryStream, uint rpcId = 0, long routeTypeOpCode = 0, long routeId = 0)

View File

@@ -16,7 +16,7 @@ namespace TEngine.Core.Network
public uint ChannelId { get; private set; }
public long LastReceiveTime { get; private set; }
public ANetworkMessageScheduler NetworkMessageScheduler { get; private set;}
private static readonly Dictionary<long, Session> Sessions = new ();
public static readonly Dictionary<long, Session> Sessions = new ();
public readonly Dictionary<long, FTask<IResponse>> RequestCallback = new();
public static void Create(ANetworkMessageScheduler networkMessageScheduler, ANetworkChannel channel, NetworkTarget networkTarget)
@@ -125,12 +125,14 @@ namespace TEngine.Core.Network
NetworkThread.Instance?.RemoveChannel(networkId, channelId);
});
}
this.NetworkId = 0;
this.ChannelId = 0;
base.Dispose();
Sessions.Remove(id);
#if NETDEBUG
Log.Debug($"Sessions Dispose Count:{Sessions.Count}");
#endif
base.Dispose();
}
public virtual void Send(object message, uint rpcId = 0, long routeId = 0)

View File

@@ -12,22 +12,21 @@ namespace TEngine.Core.Network
foreach (var sceneConfigInfo in sceneConfigInfos)
{
if (sceneConfigInfo.SceneType == "Addressable")
if (sceneConfigInfo.SceneTypeStr == "Addressable")
{
AddressableScenes.Add(sceneConfigInfo);
}
}
}
public static async FTask AddAddressable(Scene scene, long addressableId, long routeId)
public static async FTask AddAddressable(Scene scene, long addressableId, long routeId, bool isLock = true)
{
var addressableScene = AddressableScenes[(int)addressableId % AddressableScenes.Count];
var response = await MessageHelper.CallInnerRoute(scene, addressableScene.EntityId,
new I_AddressableAdd_Request
{
AddressableId = addressableId, RouteId = routeId
AddressableId = addressableId, RouteId = routeId, IsLock = isLock
});
if (response.ErrorCode != 0)
{
Log.Error($"AddAddressable error is {response.ErrorCode}");

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
namespace TEngine.Core.Network
@@ -8,13 +9,28 @@ namespace TEngine.Core.Network
private readonly Dictionary<long, WaitCoroutineLock> _locks = new();
private readonly CoroutineLockQueueType _addressableLock = new CoroutineLockQueueType("AddressableLock");
public async FTask Add(long addressableId, long routeId)
public async FTask Add(long addressableId, long routeId, bool isLock)
{
using (await _addressableLock.Lock(addressableId))
WaitCoroutineLock waitCoroutineLock = null;
try
{
if (isLock)
{
waitCoroutineLock = await _addressableLock.Lock(addressableId);
}
_addressable[addressableId] = routeId;
Log.Debug($"AddressableManageComponent Add addressableId:{addressableId} routeId:{routeId}");
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
waitCoroutineLock?.Dispose();
}
}
public async FTask<long> Get(long addressableId)
@@ -31,6 +47,7 @@ namespace TEngine.Core.Network
using (await _addressableLock.Lock(addressableId))
{
_addressable.Remove(addressableId);
Log.Debug($"Addressable Remove addressableId: {addressableId} _addressable:{_addressable.Count}");
}
}

View File

@@ -19,7 +19,7 @@ namespace TEngine.Core.Network
base.Dispose();
}
public FTask Register()
public FTask Register(bool isLock = true)
{
if (Parent == null)
{
@@ -36,7 +36,7 @@ namespace TEngine.Core.Network
#if TENGINE_DEVELOP
Log.Debug($"AddressableMessageComponent Register addressableId:{AddressableId} RouteId:{Parent.RuntimeId}");
#endif
return AddressableHelper.AddAddressable(Scene, AddressableId, Parent.RuntimeId);
return AddressableHelper.AddAddressable(Scene, AddressableId, Parent.RuntimeId, isLock);
}
public FTask Lock()

View File

@@ -1,50 +1,26 @@
#pragma warning disable CS8603
#pragma warning disable CS8600
#if TENGINE_NET
namespace TEngine.Core.Network;
public sealed class AddressableRouteComponentAwakeSystem : AwakeSystem<AddressableRouteComponent>
{
protected override void Awake(AddressableRouteComponent self)
{
self.Awake();
}
}
/// <summary>
/// 可寻址消息组件、挂载了这个组件可以接收和发送Addressable消息
/// </summary>
public sealed class AddressableRouteComponent : Entity
{
private long _parentId;
private long _addressableRouteId;
private long _routeId;
public long AddressableId { get; private set; }
public static readonly CoroutineLockQueueType AddressableRouteMessageLock = new CoroutineLockQueueType("AddressableRouteMessageLock");
public override void Dispose()
{
_parentId = 0;
_addressableRouteId = 0;
_routeId = 0;
this.AddressableId = 0;
base.Dispose();
}
public void Awake()
{
if (Parent == null)
{
throw new Exception("AddressableRouteComponent must be mounted under a component");
}
if (Parent.RuntimeId == 0)
{
throw new Exception("AddressableRouteComponent.Parent.RuntimeId is null");
}
_parentId = Parent.Id;
}
public void SetAddressableRouteId(long addressableRouteId)
public void SetAddressableId(long addressableId)
{
_addressableRouteId = addressableRouteId;
this.AddressableId = addressableId;
}
public void Send(IAddressableRouteMessage message)
@@ -68,21 +44,21 @@ public sealed class AddressableRouteComponent : Entity
var runtimeId = RuntimeId;
IResponse iRouteResponse = null;
using (await AddressableRouteMessageLock.Lock(_parentId, "AddressableRouteComponent Call MemoryStream"))
using (await AddressableRouteMessageLock.Lock(this.AddressableId, "AddressableRouteComponent Call MemoryStream"))
{
while (!IsDisposed)
{
if (_addressableRouteId == 0)
if (_routeId == 0)
{
_addressableRouteId = await AddressableHelper.GetAddressableRouteId(Scene, _parentId);
_routeId = await AddressableHelper.GetAddressableRouteId(Scene, this.AddressableId);
}
if (_addressableRouteId == 0)
if (_routeId == 0)
{
return MessageDispatcherSystem.Instance.CreateResponse(requestType, CoreErrorCode.ErrNotFoundRoute);
}
iRouteResponse = await MessageHelper.CallInnerRoute(Scene, _addressableRouteId, routeTypeOpCode, requestType, request);
iRouteResponse = await MessageHelper.CallInnerRoute(Scene, _routeId, routeTypeOpCode, requestType, request);
if (runtimeId != RuntimeId)
{
@@ -99,7 +75,7 @@ public sealed class AddressableRouteComponent : Entity
{
if (++failCount > 20)
{
Log.Error($"AddressableComponent.Call failCount > 20 route send message fail, routeId: {_addressableRouteId} AddressableRouteComponent:{Id}");
Log.Error($"AddressableComponent.Call failCount > 20 route send message fail, routeId: {_routeId} AddressableRouteComponent:{Id}");
return iRouteResponse;
}
@@ -110,7 +86,7 @@ public sealed class AddressableRouteComponent : Entity
iRouteResponse.ErrorCode = CoreErrorCode.ErrRouteTimeout;
}
_addressableRouteId = 0;
_routeId = 0;
continue;
}
default:
@@ -134,21 +110,21 @@ public sealed class AddressableRouteComponent : Entity
var failCount = 0;
var runtimeId = RuntimeId;
using (await AddressableRouteMessageLock.Lock(_parentId,"AddressableRouteComponent Call"))
using (await AddressableRouteMessageLock.Lock(this.AddressableId,"AddressableRouteComponent Call"))
{
while (true)
{
if (_addressableRouteId == 0)
if (_routeId == 0)
{
_addressableRouteId = await AddressableHelper.GetAddressableRouteId(Scene, _parentId);
_routeId = await AddressableHelper.GetAddressableRouteId(Scene, this.AddressableId);
}
if (_addressableRouteId == 0)
if (_routeId == 0)
{
return MessageDispatcherSystem.Instance.CreateResponse(request, CoreErrorCode.ErrNotFoundRoute);
}
var iRouteResponse = await MessageHelper.CallInnerRoute(Scene, _addressableRouteId, request);
var iRouteResponse = await MessageHelper.CallInnerRoute(Scene, _routeId, request);
if (runtimeId != RuntimeId)
{
@@ -161,7 +137,7 @@ public sealed class AddressableRouteComponent : Entity
{
if (++failCount > 20)
{
Log.Error($"AddressableRouteComponent.Call failCount > 20 route send message fail, routeId: {_addressableRouteId} AddressableRouteComponent:{Id}");
Log.Error($"AddressableRouteComponent.Call failCount > 20 route send message fail, routeId: {_routeId} AddressableRouteComponent:{Id}");
return iRouteResponse;
}
@@ -172,7 +148,7 @@ public sealed class AddressableRouteComponent : Entity
iRouteResponse.ErrorCode = CoreErrorCode.ErrRouteTimeout;
}
_addressableRouteId = 0;
_routeId = 0;
continue;
}
case CoreErrorCode.ErrRouteTimeout:

View File

@@ -5,7 +5,7 @@ public sealed class I_AddressableAddHandler : RouteRPC<Scene, I_AddressableAdd_R
{
protected override async FTask Run(Scene scene, I_AddressableAdd_Request request, I_AddressableAdd_Response response, Action reply)
{
await scene.GetComponent<AddressableManageComponent>().Add(request.AddressableId, request.RouteId);
await scene.GetComponent<AddressableManageComponent>().Add(request.AddressableId, request.RouteId, request.IsLock);
}
}
#endif

View File

@@ -41,7 +41,7 @@ namespace TEngine.Core.Network
scene = entity.Scene;
}
Log.Error($"SceneWorld:{session.Scene.World.Id} SceneRouteId:{scene.RouteId} SceneType:{scene.SceneInfo.SceneType} EntityId {tEntity.Id} : Error {e}");
Log.Error($"SceneWorld:{session.Scene.World.Id} ServerConfigId:{scene.Server?.Id} SceneType:{scene.SceneType} EntityId {tEntity.Id} : Error {e}");
}
}
@@ -100,7 +100,7 @@ namespace TEngine.Core.Network
scene = entity.Scene;
}
Log.Error($"SceneWorld:{session.Scene.World.Id} SceneRouteId:{scene.RouteId} SceneType:{scene.SceneInfo.SceneType} EntityId {tEntity.Id} : Error {e}");
Log.Error($"SceneWorld:{session.Scene.World?.Id} ServerConfigId:{scene.Server?.Id} SceneType:{scene.SceneType} EntityId {tEntity.Id} : Error {e}");
response.ErrorCode = CoreErrorCode.ErrRpcFail;
}
finally
@@ -144,7 +144,7 @@ namespace TEngine.Core.Network
scene = entity.Scene;
}
Log.Error($"SceneWorld:{session.Scene.World.Id} SceneRouteId:{scene.RouteId} SceneType:{scene.SceneInfo.SceneType} EntityId {tEntity.Id} : Error {e}");
Log.Error($"SceneWorld:{session.Scene.World.Id} ServerConfigId:{scene.Server?.Id} SceneType:{scene.SceneType} EntityId {tEntity.Id} : Error {e}");
}
finally
{
@@ -207,7 +207,7 @@ namespace TEngine.Core.Network
scene = entity.Scene;
}
Log.Error($"SceneWorld:{session.Scene.World.Id} SceneRouteId:{scene.RouteId} SceneType:{scene.SceneInfo.SceneType} EntityId {tEntity.Id} : Error {e}");
Log.Error($"SceneWorld:{session.Scene.World?.Id} ServerConfigId:{scene.Server?.Id} SceneType:{scene.SceneType} EntityId {tEntity.Id} : Error {e}");
response.ErrorCode = CoreErrorCode.ErrRpcFail;
}
finally

View File

@@ -1,18 +1,17 @@
using System;
using System.IO;
using TEngine.Core;
#pragma warning disable CS8600
namespace TEngine.Core.Network
{
public abstract class ANetworkMessageScheduler
{
protected bool DisposePackInfo;
private readonly PingResponse _pingResponse = new PingResponse();
public async FTask Scheduler(Session session, APackInfo packInfo)
{
Type messageType = null;
var packInfoMemoryStream = packInfo.MemoryStream;
DisposePackInfo = true;
try
{
@@ -56,17 +55,6 @@ namespace TEngine.Core.Network
// 服务器之间发送消息因为走的是MessageHelper、所以接收消息的回调也应该放到MessageHelper里处理
MessageHelper.ResponseHandler(packInfo.RpcId, aResponse);
#else
#if TENGINE_UNITY
if (MessageDispatcherSystem.Instance.MsgHandles.TryGetValue(packInfo.ProtocolCode,out var msgDelegates))
{
foreach (var msgDelegate in msgDelegates)
{
msgDelegate.Invoke(aResponse);
}
return;
}
#endif
// 这个一般是客户端Session.Call发送时使用的、目前这个逻辑只有Unity客户端时使用
if (!session.RequestCallback.TryGetValue(packInfo.RpcId, out var action))
@@ -84,20 +72,57 @@ namespace TEngine.Core.Network
}
catch (Exception e)
{
if (packInfoMemoryStream.CanRead)
{
// ReSharper disable once MethodHasAsyncOverload
packInfoMemoryStream.Dispose();
}
Log.Error($"NetworkMessageScheduler error messageProtocolCode:{packInfo.ProtocolCode} messageType:{messageType} SessionId {session.Id} IsDispose {session.IsDisposed} {e}");
}
finally
{
packInfo.Dispose();
if (DisposePackInfo)
{
NetworkThread.Instance.SynchronizationContext.Post(packInfo.Dispose);
}
}
}
public async FTask InnerScheduler(Session session, uint rpcId, long routeId, uint protocolCode, long routeTypeCode, object message)
{
var messageType = message.GetType();
try
{
if (session.IsDisposed)
{
return;
}
switch (protocolCode)
{
case >= Opcode.OuterRouteMessage:
{
await InnerHandler(session, rpcId, routeId, protocolCode, routeTypeCode, messageType, message);
return;
}
case < Opcode.OuterResponse:
{
MessageDispatcherSystem.Instance.MessageHandler(session, messageType, message, rpcId, protocolCode);
return;
}
default:
{
#if TENGINE_NET
// 服务器之间发送消息因为走的是MessageHelper、所以接收消息的回调也应该放到MessageHelper里处理
MessageHelper.ResponseHandler(rpcId, (IResponse)message);
#endif
return;
}
}
}
catch (Exception e)
{
Log.Error($"NetworkMessageScheduler error messageProtocolCode:{protocolCode} messageType:{messageType} SessionId {session.Id} IsDispose {session.IsDisposed} {e}");
}
}
protected abstract FTask Handler(Session session, Type messageType, APackInfo packInfo);
protected abstract FTask InnerHandler(Session session, uint rpcId, long routeId, uint protocolCode, long routeTypeCode, Type messageType, object message);
}
}

View File

@@ -35,7 +35,7 @@ public static class MessageHelper
}
EntityIdStruct entityIdStruct = entityId;
var session = scene.Server.GetSession(entityIdStruct.RouteId);
var session = scene.Server.GetSession(entityIdStruct.LocationId);
session.Send(message, 0, entityId);
}
@@ -48,7 +48,7 @@ public static class MessageHelper
}
EntityIdStruct entityIdStruct = entityId;
var session = scene.Server.GetSession(entityIdStruct.RouteId);
var session = scene.Server.GetSession(entityIdStruct.LocationId);
session.Send(message, 0, routeTypeOpCode, entityId);
}
@@ -81,7 +81,7 @@ public static class MessageHelper
EntityIdStruct entityIdStruct = entityId;
var rpcId = ++_rpcId;
var session = scene.Server.GetSession(entityIdStruct.RouteId);
var session = scene.Server.GetSession(entityIdStruct.LocationId);
var requestCallback = FTask<IResponse>.Create(false);
RequestCallback.Add(rpcId, MessageSender.Create(rpcId, requestType, requestCallback));
session.Send(request, rpcId, routeTypeOpCode, entityId);
@@ -98,7 +98,7 @@ public static class MessageHelper
EntityIdStruct entityIdStruct = entityId;
var rpcId = ++_rpcId;
var session = scene.Server.GetSession(entityIdStruct.RouteId);
var session = scene.Server.GetSession(entityIdStruct.LocationId);
var requestCallback = FTask<IResponse>.Create(false);
RequestCallback.Add(rpcId, MessageSender.Create(rpcId, request, requestCallback));
session.Send(request, rpcId, entityId);

View File

@@ -63,6 +63,8 @@ namespace TEngine
public long AddressableId { get; set; }
[ProtoMember(2)]
public long RouteId { get; set; }
[ProtoMember(3)]
public bool IsLock { get; set; }
}
[ProtoContract]
public partial class I_AddressableAdd_Response : AProto, IRouteResponse

View File

@@ -6,31 +6,31 @@ namespace TEngine.Core.Network;
/// </summary>
public sealed class RouteComponent : Entity
{
private readonly Dictionary<long, long> _routeAddress = new Dictionary<long, long>();
public readonly Dictionary<long, long> RouteAddress = new Dictionary<long, long>();
public void AddAddress(long routeType, long routeId)
{
_routeAddress.Add(routeType, routeId);
this.RouteAddress.Add(routeType, routeId);
}
public void RemoveAddress(long routeType)
{
_routeAddress.Remove(routeType);
this.RouteAddress.Remove(routeType);
}
public long GetRouteId(long routeType)
{
return _routeAddress.TryGetValue(routeType, out var routeId) ? routeId : 0;
return this.RouteAddress.TryGetValue(routeType, out var routeId) ? routeId : 0;
}
public bool TryGetRouteId(long routeType, out long routeId)
{
return _routeAddress.TryGetValue(routeType, out routeId);
return this.RouteAddress.TryGetValue(routeType, out routeId);
}
public override void Dispose()
{
_routeAddress.Clear();
this.RouteAddress.Clear();
base.Dispose();
}
}

View File

@@ -7,8 +7,6 @@ namespace TEngine.Core.Network
{
protected override async FTask Handler(Session session, Type messageType, APackInfo packInfo)
{
var packInfoMemoryStream = packInfo.MemoryStream;
try
{
switch (packInfo.ProtocolCode)
@@ -28,7 +26,7 @@ namespace TEngine.Core.Network
Log.Error($"not found rpc {packInfo.RpcId}, response message: {aResponse.GetType().Name}");
return;
}
session.RequestCallback.Remove(packInfo.RpcId);
action.SetResult(aResponse);
return;
@@ -43,19 +41,22 @@ namespace TEngine.Core.Network
}
catch (Exception e)
{
if (packInfoMemoryStream.CanRead)
{
// ReSharper disable once MethodHasAsyncOverload
packInfoMemoryStream.Dispose();
}
Log.Error(e);
return;
}
finally
{
NetworkThread.Instance.SynchronizationContext.Post(packInfo.Dispose);
}
await FTask.CompletedTask;
throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}");
}
protected override FTask InnerHandler(Session session, uint rpcId, long routeId, uint protocolCode, long routeTypeCode, Type messageType, object message)
{
throw new NotImplementedException();
}
}
#endif
#if TENGINE_NET
@@ -65,6 +66,11 @@ namespace TEngine.Core.Network
{
throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}");
}
protected override FTask InnerHandler(Session session, uint rpcId, long routeId, uint protocolCode, long routeTypeCode, Type messageType, object message)
{
throw new NotSupportedException($"Received unsupported message protocolCode:{protocolCode} messageType:{messageType}");
}
}
#endif
}

View File

@@ -7,11 +7,10 @@ namespace TEngine.Core.Network
{
protected override async FTask Handler(Session session, Type messageType, APackInfo packInfo)
{
var disposeMemoryStream = true;
var packInfoMemoryStream = packInfo.MemoryStream;
try
{
DisposePackInfo = false;
switch (packInfo.ProtocolCode)
{
case >= Opcode.InnerBsonRouteResponse:
@@ -80,8 +79,7 @@ namespace TEngine.Core.Network
case Session gateSession:
{
// 这里如果是Session只可能是Gate的Session、如果是的话、肯定是转发Address消息
disposeMemoryStream = false;
gateSession.Send(packInfoMemoryStream, packInfo.RpcId);
gateSession.Send(packInfo.CreateMemoryStream(), packInfo.RpcId);
return;
}
default:
@@ -94,20 +92,108 @@ namespace TEngine.Core.Network
}
default:
{
throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}");
throw new NotSupportedException(
$"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}");
}
}
}
catch (Exception e)
{
if (disposeMemoryStream && packInfoMemoryStream.CanRead)
{
// ReSharper disable once MethodHasAsyncOverload
packInfoMemoryStream.Dispose();
}
Log.Error($"InnerMessageSchedulerHandler error messageProtocolCode:{packInfo.ProtocolCode} messageType:{messageType} {e}");
}
finally
{
NetworkThread.Instance.SynchronizationContext.Post(packInfo.Dispose);
}
}
protected override async FTask InnerHandler(Session session, uint rpcId, long routeId, uint protocolCode, long routeTypeCode, Type messageType, object message)
{
try
{
switch (protocolCode)
{
case >= Opcode.InnerBsonRouteResponse:
case >= Opcode.InnerRouteResponse:
{
MessageHelper.ResponseHandler(rpcId, (IRouteResponse)message);
return;
}
case >= Opcode.OuterRouteResponse:
{
// 如果Gate服务器、需要转发Addressable协议、所以这里有可能会接收到该类型协议
MessageHelper.ResponseHandler(rpcId, (IResponse)message);
return;
}
case > Opcode.InnerBsonRouteMessage:
{
var entity = Entity.GetEntity(routeId);
if (entity == null)
{
if (protocolCode > Opcode.InnerBsonRouteRequest)
{
MessageDispatcherSystem.Instance.FailResponse(session, (IRouteRequest)message, CoreErrorCode.ErrNotFoundRoute, rpcId);
}
return;
}
await MessageDispatcherSystem.Instance.RouteMessageHandler(session, messageType, entity, message, rpcId);
return;
}
case > Opcode.InnerRouteMessage:
{
var entity = Entity.GetEntity(routeId);
if (entity == null)
{
if (protocolCode > Opcode.InnerRouteRequest)
{
MessageDispatcherSystem.Instance.FailResponse(session, (IRouteRequest)message, CoreErrorCode.ErrNotFoundRoute, rpcId);
}
return;
}
await MessageDispatcherSystem.Instance.RouteMessageHandler(session, messageType, entity, message, rpcId);
return;
}
case > Opcode.OuterRouteMessage:
{
var entity = Entity.GetEntity(routeId);
switch (entity)
{
case null:
{
var response = MessageDispatcherSystem.Instance.CreateResponse((IRouteMessage)message, CoreErrorCode.ErrNotFoundRoute);
session.Send(response, rpcId, routeId);
return;
}
case Session gateSession:
{
// 这里如果是Session只可能是Gate的Session、如果是的话、肯定是转发Address消息
gateSession.Send(message, rpcId);
return;
}
default:
{
await MessageDispatcherSystem.Instance.RouteMessageHandler(session, messageType, entity, message, rpcId);
return;
}
}
}
default:
{
throw new NotSupportedException($"Received unsupported message protocolCode:{protocolCode} messageType:{messageType}");
}
}
}
catch (Exception e)
{
Log.Error($"InnerMessageSchedulerHandler error messageProtocolCode:{protocolCode} messageType:{messageType} {e}");
}
}
}
}

View File

@@ -11,6 +11,11 @@ namespace TEngine.Core.Network
{
throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}");
}
protected override FTask InnerHandler(Session session, uint rpcId, long routeId, uint protocolCode, long routeTypeCode, Type messageType, object message)
{
throw new NotImplementedException();
}
}
#endif
#if TENGINE_NET
@@ -23,10 +28,10 @@ namespace TEngine.Core.Network
throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}");
}
var packInfoMemoryStream = packInfo.MemoryStream;
try
{
DisposePackInfo = false;
switch (packInfo.RouteTypeCode)
{
case CoreRouteType.Route:
@@ -49,10 +54,8 @@ namespace TEngine.Core.Network
case > Opcode.OuterRouteRequest:
{
var runtimeId = session.RuntimeId;
var response = await addressableRouteComponent.Call(packInfo.RouteTypeCode, messageType, packInfoMemoryStream);
var response = await addressableRouteComponent.Call(packInfo.RouteTypeCode, messageType, packInfo.CreateMemoryStream());
// session可能已经断开了所以这里需要判断
if (session.RuntimeId == runtimeId)
{
session.Send(response, packInfo.RpcId);
@@ -62,7 +65,7 @@ namespace TEngine.Core.Network
}
case > Opcode.OuterRouteMessage:
{
addressableRouteComponent.Send(packInfo.RouteTypeCode, messageType, packInfoMemoryStream);
addressableRouteComponent.Send(packInfo.RouteTypeCode, messageType, packInfo.CreateMemoryStream());
return;
}
}
@@ -90,7 +93,7 @@ namespace TEngine.Core.Network
case > Opcode.OuterRouteRequest:
{
var runtimeId = session.RuntimeId;
var response = await MessageHelper.CallInnerRoute(session.Scene, routeId, packInfo.RouteTypeCode, messageType, packInfoMemoryStream);
var response = await MessageHelper.CallInnerRoute(session.Scene, routeId, packInfo.RouteTypeCode, messageType, packInfo.CreateMemoryStream());
// session可能已经断开了所以这里需要判断
if (session.RuntimeId == runtimeId)
{
@@ -101,7 +104,7 @@ namespace TEngine.Core.Network
}
case > Opcode.OuterRouteMessage:
{
MessageHelper.SendInnerRoute(session.Scene, routeId, packInfo.RouteTypeCode, packInfoMemoryStream);
MessageHelper.SendInnerRoute(session.Scene, routeId, packInfo.RouteTypeCode, packInfo.CreateMemoryStream());
return;
}
}
@@ -112,18 +115,21 @@ namespace TEngine.Core.Network
}
catch (Exception e)
{
if (packInfoMemoryStream.CanRead)
{
// ReSharper disable once MethodHasAsyncOverload
packInfoMemoryStream.Dispose();
}
Log.Error(e);
return;
}
finally
{
NetworkThread.Instance.SynchronizationContext.Post(packInfo.Dispose);
}
throw new NotSupportedException($"Received unsupported message protocolCode:{packInfo.ProtocolCode} messageType:{messageType}");
}
protected override FTask InnerHandler(Session session, uint rpcId, long routeId, uint protocolCode, long routeTypeCode, Type messageType, object message)
{
throw new NotSupportedException($"OuterMessageScheduler NotSupported InnerHandler");
}
}
#endif
}

View File

@@ -1,218 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace TEngine.Core.Network
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int KcpOutput(IntPtr buf, int len, IntPtr kcp, IntPtr user);
public static class KCP
{
#if UNITY_IPHONE && !UNITY_EDITOR
const string KcpDll = "__Internal";
#else
const string KcpDll = "kcp";
#endif
[DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)]
private static extern uint ikcp_check(IntPtr kcp, uint current);
[DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr ikcp_create(uint conv, IntPtr user);
[DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)]
private static extern void ikcp_flush(IntPtr kcp);
[DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)]
private static extern uint ikcp_getconv(IntPtr ptr);
[DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)]
private static extern int ikcp_input(IntPtr kcp, byte[] data, int offset, int size);
[DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)]
private static extern int ikcp_nodelay(IntPtr kcp, int nodelay, int interval, int resend, int nc);
[DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)]
private static extern int ikcp_peeksize(IntPtr kcp);
[DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)]
private static extern int ikcp_recv(IntPtr kcp, byte[] buffer, int len);
[DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)]
private static extern void ikcp_release(IntPtr kcp);
[DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)]
private static extern int ikcp_send(IntPtr kcp, byte[] buffer, int len);
[DllImport(KcpDll, CallingConvention=CallingConvention.Cdecl)]
private static extern void ikcp_setminrto(IntPtr ptr, int minrto);
[DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)]
private static extern int ikcp_setmtu(IntPtr kcp, int mtu);
[DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)]
private static extern void ikcp_setoutput(IntPtr kcp, KcpOutput output);
[DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)]
private static extern void ikcp_update(IntPtr kcp, uint current);
[DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)]
private static extern int ikcp_waitsnd(IntPtr kcp);
[DllImport(KcpDll, CallingConvention = CallingConvention.Cdecl)]
private static extern int ikcp_wndsize(IntPtr kcp, int sndwnd, int rcvwnd);
public static uint KcpCheck(IntPtr kcp, uint current)
{
if (kcp == IntPtr.Zero)
{
throw new Exception($"kcp error, kcp point is zero");
}
return ikcp_check(kcp, current);
}
public static IntPtr KcpCreate(uint conv, IntPtr user)
{
return ikcp_create(conv, user);
}
public static void KcpFlush(IntPtr kcp)
{
if (kcp == IntPtr.Zero)
{
throw new Exception($"kcp error, kcp point is zero");
}
ikcp_flush(kcp);
}
public static uint KcpGetconv(IntPtr ptr)
{
if (ptr == IntPtr.Zero)
{
throw new Exception($"kcp error, kcp point is zero");
}
return ikcp_getconv(ptr);
}
public static int KcpInput(IntPtr kcp, byte[] data, int offset, int size)
{
if (kcp == IntPtr.Zero)
{
throw new Exception($"kcp error, kcp point is zero");
}
return ikcp_input(kcp, data, offset, size);
}
public static int KcpNodelay(IntPtr kcp, int nodelay, int interval, int resend, int nc)
{
if (kcp == IntPtr.Zero)
{
throw new Exception($"kcp error, kcp point is zero");
}
return ikcp_nodelay(kcp, nodelay, interval, resend, nc);
}
public static int KcpPeeksize(IntPtr kcp)
{
if (kcp == IntPtr.Zero)
{
throw new Exception($"kcp error, kcp point is zero");
}
return ikcp_peeksize(kcp);
}
public static int KcpRecv(IntPtr kcp, byte[] buffer, int len)
{
if (kcp == IntPtr.Zero)
{
throw new Exception($"kcp error, kcp point is zero");
}
return ikcp_recv(kcp, buffer, len);
}
public static void KcpRelease(IntPtr kcp)
{
if (kcp == IntPtr.Zero)
{
throw new Exception($"kcp error, kcp point is zero");
}
ikcp_release(kcp);
}
public static int KcpSend(IntPtr kcp, byte[] buffer, int len)
{
if (kcp == IntPtr.Zero)
{
throw new Exception($"kcp error, kcp point is zero");
}
return ikcp_send(kcp, buffer, len);
}
public static void KcpSetminrto(IntPtr kcp, int minrto)
{
if (kcp == IntPtr.Zero)
{
throw new Exception($"kcp error, kcp point is zero");
}
ikcp_setminrto(kcp, minrto);
}
public static int KcpSetmtu(IntPtr kcp, int mtu)
{
if (kcp == IntPtr.Zero)
{
throw new Exception($"kcp error, kcp point is zero");
}
return ikcp_setmtu(kcp, mtu);
}
public static void KcpSetoutput(IntPtr kcp, KcpOutput output)
{
if (kcp == IntPtr.Zero)
{
throw new Exception($"kcp error, kcp point is zero");
}
ikcp_setoutput(kcp, output);
}
public static void KcpUpdate(IntPtr kcp, uint current)
{
if (kcp == IntPtr.Zero)
{
throw new Exception($"kcp error, kcp point is zero");
}
ikcp_update(kcp, current);
}
public static int KcpWaitsnd(IntPtr kcp)
{
if (kcp == IntPtr.Zero)
{
throw new Exception($"kcp error, kcp point is zero");
}
return ikcp_waitsnd(kcp);
}
public static int KcpWndsize(IntPtr kcp, int sndwnd, int rcvwnd)
{
if (kcp == IntPtr.Zero)
{
throw new Exception($"kcp error, kcp point is zero");
}
return ikcp_wndsize(kcp, sndwnd, rcvwnd);
}
}
}

View File

@@ -4,9 +4,9 @@ namespace TEngine.Core.Network
{
public class KCPSettings
{
public int Mtu { get; private set; }
public int SendWindowSize { get; private set; }
public int ReceiveWindowSize { get; private set; }
public uint Mtu { get; private set; }
public uint SendWindowSize { get; private set; }
public uint ReceiveWindowSize { get; private set; }
public int MaxSendWindowSize { get; private set; }
public static KCPSettings Create(NetworkTarget networkTarget)

View File

@@ -1,14 +0,0 @@
namespace TEngine
{
public static class KcpProtocalType
{
public const byte SYN = 1;
public const byte ACK = 2;
public const byte FIN = 3;
public const byte MSG = 4;
public const byte RouterReconnectSYN = 5;
public const byte RouterReconnectACK = 6;
public const byte RouterSYN = 7;
public const byte RouterACK = 8;
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 9bd29f721d3e458d88d43160fd7cca55
timeCreated: 1689230822

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 6ec9cf984eeccbb4ba7470e5beeac5a1
guid: b6517b73296f4564a8411993d86fcafb
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -0,0 +1,24 @@
MIT License
Copyright (c) 2016 limpo1989
Copyright (c) 2020 Paul Pacheco
Copyright (c) 2020 Lymdun
Copyright (c) 2020 vis2k
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 72dcdf060ecf34d279acdee20e77b200
guid: 60e959aa2b0fc774fb97f3a52197e41b
DefaultImporter:
externalObjects: {}
userData:

View File

@@ -0,0 +1,24 @@
MIT License
Copyright (c) 2016 limpo1989
Copyright (c) 2020 Paul Pacheco
Copyright (c) 2020 Lymdun
Copyright (c) 2020 vis2k
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e29e0db9c79ad6348aa2997ac1326446
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,32 @@
# kcp2k
C# KCP based on the original C [kcp](https://github.com/skywind3000/kcp).
Works with **netcore** and **Unity**.
Developed for [Mirror Networking](https://github.com/MirrorNetworking/Mirror).
# Features
* Kcp.cs based on kcp.c v1.7, line-by-line translation to C#
* Heavy test coverage
* Fixed [WND_RCV bug](https://github.com/skywind3000/kcp/pull/291) from original kcp
* Optional high level C# code for client/server connection handling
* Optional high level Unreliable channel added
Pull requests for bug fixes & tests welcome.
# Unity
kcp2k works perfectly with Unity, see the Mirror repository's KcpTransport.
# Allocations
The client is allocation free.
The server's SendTo/ReceiveFrom still allocate.
Previously, [where-allocation](https://github.com/vis2k/where-allocation) for a 25x reduction in server allocations. However:
- It only worked with Unity's old Mono version.
- It didn't work in Unity's IL2CPP builds, which are still faster than Mono + NonAlloc
- It didn't work in regular C# projects.
- Overall, the extra complexity is not worth it. Use IL2CPP instead.
- Microsoft is considering to [remove the allocation](https://github.com/dotnet/runtime/issues/30797#issuecomment-1308599410).
# Remarks
- **Congestion Control** should be left disabled. It seems to be broken in KCP.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 2e2e6b3195633174f85b149e87c9312b
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,234 @@
V1.36 [2023-06-08]
- fix: #49 KcpPeer.RawInput message size check now considers cookie as well
- kcp.cs cleanups
V1.35 [2023-04-05]
- fix: KcpClients now need to validate with a secure cookie in order to protect against
UDP spoofing. fixes:
https://github.com/MirrorNetworking/Mirror/issues/3286
[disclosed by IncludeSec]
- KcpClient/Server: change callbacks to protected so inheriting classes can use them too
- KcpClient/Server: change config visibility to protected
V1.34 [2023-03-15]
- Send/SendTo/Receive/ReceiveFrom NonBlocking extensions.
to encapsulate WouldBlock allocations, exceptions, etc.
allows for reuse when overwriting KcpServer/Client (i.e. for relays).
V1.33 [2023-03-14]
- perf: KcpServer/Client RawReceive now call socket.Poll to avoid non-blocking
socket's allocating a new SocketException in case they WouldBlock.
fixes https://github.com/MirrorNetworking/Mirror/issues/3413
- perf: KcpServer/Client RawSend now call socket.Poll to avoid non-blocking
socket's allocating a new SocketException in case they WouldBlock.
fixes https://github.com/MirrorNetworking/Mirror/issues/3413
V1.32 [2023-03-12]
- fix: KcpPeer RawInput now doesn't disconnect in case of random internet noise
V1.31 [2023-03-05]
- KcpClient: Tick/Incoming/Outgoing can now be overwritten (virtual)
- breaking: KcpClient now takes KcpConfig in constructor instead of in Connect.
cleaner, and prepares for KcpConfig.MTU setting.
- KcpConfig now includes MTU; KcpPeer now works with KcpConfig's MTU, KcpServer/Client
buffers are now created with config's MTU.
V1.30 [2023-02-20]
- fix: set send/recv buffer sizes directly instead of iterating to find the limit.
fixes: https://github.com/MirrorNetworking/Mirror/issues/3390
- fix: server & client sockets are now always non-blocking to ensure main thread never
blocks on socket.recv/send. Send() now also handles WouldBlock.
- fix: socket.Receive/From directly with non-blocking sockets and handle WouldBlock,
instead of socket.Poll. faster, more obvious, and fixes Poll() looping forever while
socket is in error state. fixes: https://github.com/MirrorNetworking/Mirror/issues/2733
V1.29 [2023-01-28]
- fix: KcpServer.CreateServerSocket now handles NotSupportedException when setting DualMode
https://github.com/MirrorNetworking/Mirror/issues/3358
V1.28 [2023-01-28]
- fix: KcpClient.Connect now resolves hostname before creating peer
https://github.com/MirrorNetworking/Mirror/issues/3361
V1.27 [2023-01-08]
- KcpClient.Connect: invoke own events directly instead of going through peer,
which calls our own events anyway
- fix: KcpPeer/Client/Server callbacks are readonly and assigned in constructor
to ensure they are safe to use at all times.
fixes https://github.com/MirrorNetworking/Mirror/issues/3337
V1.26 [2022-12-22]
- KcpPeer.RawInput: fix compile error in old Unity Mono versions
- fix: KcpServer sets up a new connection's OnError immediately.
fixes KcpPeer throwing NullReferenceException when attempting to call OnError
after authentication errors.
- improved log messages
V1.25 [2022-12-14]
- breaking: removed where-allocation. use IL2CPP on servers instead.
- breaking: KcpConfig to simplify configuration
- high level cleanups
V1.24 [2022-12-14]
- KcpClient: fixed NullReferenceException when connection without a server.
added test coverage to ensure this never happens again.
V1.23 [2022-12-07]
- KcpClient: rawReceiveBuffer exposed
- fix: KcpServer RawSend uses connection.remoteEndPoint instead of the helper
'newClientEP'. fixes clients receiving the wrong messages meant for others.
https://github.com/MirrorNetworking/Mirror/issues/3296
V1.22 [2022-11-30]
- high level refactor, part two.
V1.21 [2022-11-24]
- high level refactor, part one.
- KcpPeer instead of KcpConnection, KcpClientConnection, KcpServerConnection
- RawSend/Receive can now easily be overwritten in KcpClient/Server.
for non-alloc, relays, etc.
V1.20 [2022-11-22]
- perf: KcpClient receive allocation was removed entirely.
reduces Mirror benchmark client sided allocations from 4.9 KB / 1.7 KB (non-alloc) to 0B.
- fix: KcpConnection.Disconnect does not check socket.Connected anymore.
UDP sockets don't have a connection.
fixes Disconnects not being sent to clients in netcore.
- KcpConnection.SendReliable: added OnError instead of logs
V1.19 [2022-05-12]
- feature: OnError ErrorCodes
V1.18 [2022-05-08]
- feature: OnError to allow higher level to show popups etc.
- feature: KcpServer.GetClientAddress is now GetClientEndPoint in order to
expose more details
- ResolveHostname: include exception in log for easier debugging
- fix: KcpClientConnection.RawReceive now logs the SocketException even if
it was expected. makes debugging easier.
- fix: KcpServer.TickIncoming now logs the SocketException even if it was
expected. makes debugging easier.
- fix: KcpClientConnection.RawReceive now calls Disconnect() if the other end
has closed the connection. better than just remaining in a state with unusable
sockets.
V1.17 [2022-01-09]
- perf: server/client MaximizeSendReceiveBuffersToOSLimit option to set send/recv
buffer sizes to OS limit. avoids drops due to small buffers under heavy load.
V1.16 [2022-01-06]
- fix: SendUnreliable respects ArraySegment.Offset
- fix: potential bug with negative length (see PR #2)
- breaking: removed pause handling because it's not necessary for Mirror anymore
V1.15 [2021-12-11]
- feature: feature: MaxRetransmits aka dead_link now configurable
- dead_link disconnect message improved to show exact retransmit count
V1.14 [2021-11-30]
- fix: Send() now throws an exception for messages which require > 255 fragments
- fix: ReliableMaxMessageSize is now limited to messages which require <= 255 fragments
V1.13 [2021-11-28]
- fix: perf: uncork max message size from 144 KB to as much as we want based on
receive window size.
fixes https://github.com/vis2k/kcp2k/issues/22
fixes https://github.com/skywind3000/kcp/pull/291
- feature: OnData now includes channel it was received on
V1.12 [2021-07-16]
- Tests: don't depend on Unity anymore
- fix: #26 - Kcp now catches exception if host couldn't be resolved, and calls
OnDisconnected to let the user now.
- fix: KcpServer.DualMode is now configurable in the constructor instead of
using #if UNITY_SWITCH. makes it run on all other non dual mode platforms too.
- fix: where-allocation made optional via virtuals and inheriting
KcpServer/Client/Connection NonAlloc classes. fixes a bug where some platforms
might not support where-allocation.
V1.11 rollback [2021-06-01]
- perf: Segment MemoryStream initial capacity set to MTU to avoid early runtime
resizing/allocations
V1.10 [2021-05-28]
- feature: configurable Timeout
- allocations explained with comments (C# ReceiveFrom / IPEndPoint.GetHashCode)
- fix: #17 KcpConnection.ReceiveNextReliable now assigns message default so it
works in .net too
- fix: Segment pool is not static anymore. Each kcp instance now has it's own
Pool<Segment>. fixes #18 concurrency issues
V1.9 [2021-03-02]
- Tick() split into TickIncoming()/TickOutgoing() to use in Mirror's new update
functions. allows to minimize latency.
=> original Tick() is still supported for convenience. simply processes both!
V1.8 [2021-02-14]
- fix: Unity IPv6 errors on Nintendo Switch
- fix: KcpConnection now disconnects if data message was received without content.
previously it would call OnData with an empty ArraySegment, causing all kinds of
weird behaviour in Mirror/DOTSNET. Added tests too.
- fix: KcpConnection.SendData: don't allow sending empty messages anymore. disconnect
and log a warning to make it completely obvious.
V1.7 [2021-01-13]
- fix: unreliable messages reset timeout now too
- perf: KcpConnection OnCheckEnabled callback changed to a simple 'paused' boolean.
This is faster than invoking a Func<bool> every time and allows us to fix #8 more
easily later by calling .Pause/.Unpause from OnEnable/OnDisable in MirrorTransport.
- fix #8: Unpause now resets timeout to fix a bug where Mirror would pause kcp,
change the scene which took >10s, then unpause and kcp would detect the lack of
any messages for >10s as timeout. Added test to make sure it never happens again.
- MirrorTransport: statistics logging for headless servers
- Mirror Transport: Send/Receive window size increased once more from 2048 to 4096.
V1.6 [2021-01-10]
- Unreliable channel added!
- perf: KcpHeader byte added to every kcp message to indicate
Handshake/Data/Ping/Disconnect instead of scanning each message for Hello/Byte/Ping
content via SegmentEquals. It's a lot cleaner, should be faster and should avoid
edge cases where a message content would equal Hello/Ping/Bye sequence accidentally.
- Kcp.Input: offset moved to parameters for cases where it's needed
- Kcp.SetMtu from original Kcp.c
V1.5 [2021-01-07]
- KcpConnection.MaxSend/ReceiveRate calculation based on the article
- MirrorTransport: large send/recv window size defaults to avoid high latencies caused
by packets not being processed fast enough
- MirrorTransport: show MaxSend/ReceiveRate in debug gui
- MirrorTransport: don't Log.Info to console in headless mode if debug log is disabled
V1.4 [2020-11-27]
- fix: OnCheckEnabled added. KcpConnection message processing while loop can now
be interrupted immediately. fixes Mirror Transport scene changes which need to stop
processing any messages immediately after a scene message)
- perf: Mirror KcpTransport: FastResend enabled by default. turbo mode according to:
https://github.com/skywind3000/kcp/blob/master/README.en.md#protocol-configuration
- perf: Mirror KcpTransport: CongestionControl disabled by default (turbo mode)
V1.3 [2020-11-17]
- Log.Info/Warning/Error so logging doesn't depend on UnityEngine anymore
- fix: Server.Tick catches SocketException which happens if Android client is killed
- MirrorTransport: debugLog option added that can be checked in Unity Inspector
- Utils.Clamp so Kcp.cs doesn't depend on UnityEngine
- Utils.SegmentsEqual: use Linq SequenceEqual so it doesn't depend on UnityEngine
=> kcp2k can now be used in any C# project even without Unity
V1.2 [2020-11-10]
- more tests added
- fix: raw receive buffers are now all of MTU size
- fix: raw receive detects error where buffer was too small for msgLength and
result in excess data being dropped silently
- KcpConnection.MaxMessageSize added for use in high level
- KcpConnection.MaxMessageSize increased from 1200 bytes to to maximum allowed
message size of 145KB for kcp (based on mtu, overhead, wnd_rcv)
V1.1 [2020-10-30]
- high level cleanup, fixes, improvements
V1.0 [2020-10-22]
- Kcp.cs now mirrors original Kcp.c behaviour
(this fixes dozens of bugs)
V0.1
- initial kcp-csharp based version

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0d1fd9853ebb3d047acd92fae8441350
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 1c12ef610b8f30c46996cf5250001b11
guid: d84ce75a45b625749bb2ee11dadf14e9
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -0,0 +1,8 @@
namespace kcp2k
{
internal struct AckItem
{
internal uint serialNumber;
internal uint timestamp;
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 0f463cb65a417438aa4f903494e1d35d
guid: cbc45820dcb825444a1e4b3a1bcba94a
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("kcp2k.Tests")]

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 1d82572d4fcf54412ab612b4ed23a2df
guid: 1731c38e547afe045bd600f9082a0f33
MonoImporter:
externalObjects: {}
serializedVersion: 2

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,46 @@
// Pool to avoid allocations (from libuv2k & Mirror)
using System;
using System.Collections.Generic;
namespace kcp2k
{
public class Pool<T>
{
// Mirror is single threaded, no need for concurrent collections
readonly Stack<T> objects = new Stack<T>();
// some types might need additional parameters in their constructor, so
// we use a Func<T> generator
readonly Func<T> objectGenerator;
// some types might need additional cleanup for returned objects
readonly Action<T> objectResetter;
public Pool(Func<T> objectGenerator, Action<T> objectResetter, int initialCapacity)
{
this.objectGenerator = objectGenerator;
this.objectResetter = objectResetter;
// allocate an initial pool so we have fewer (if any)
// allocations in the first few frames (or seconds).
for (int i = 0; i < initialCapacity; ++i)
objects.Push(objectGenerator());
}
// take an element from the pool, or create a new one if empty
public T Take() => objects.Count > 0 ? objects.Pop() : objectGenerator();
// return an element to the pool
public void Return(T item)
{
objectResetter(item);
objects.Push(item);
}
// clear the pool
public void Clear() => objects.Clear();
// count to see how many objects are in the pool. useful for tests.
public int Count => objects.Count;
}
}

View File

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

View File

@@ -0,0 +1,78 @@
using System.IO;
namespace kcp2k
{
// KCP Segment Definition
internal class Segment
{
internal uint conv; // conversation
internal uint cmd; // command, e.g. Kcp.CMD_ACK etc.
// fragment (sent as 1 byte).
// 0 if unfragmented, otherwise fragment numbers in reverse: N,..,32,1,0
// this way the first received segment tells us how many fragments there are.
internal uint frg;
internal uint wnd; // window size that the receive can currently receive
internal uint ts; // timestamp
internal uint sn; // sequence number
internal uint una;
internal uint resendts; // resend timestamp
internal int rto;
internal uint fastack;
internal uint xmit; // retransmit count
// we need an auto scaling byte[] with a WriteBytes function.
// MemoryStream does that perfectly, no need to reinvent the wheel.
// note: no need to pool it, because Segment is already pooled.
// -> default MTU as initial capacity to avoid most runtime resizing/allocations
//
// .data is only used for Encode(), which always fits it into a buffer.
// the buffer is always Kcp.buffer. Kcp ctor creates the buffer of size:
// (mtu + OVERHEAD) * 3 bytes.
// in other words, Encode only ever writes up to the above amount of bytes.
internal MemoryStream data = new MemoryStream(Kcp.MTU_DEF);
// ikcp_encode_seg
// encode a segment into buffer.
// buffer is always Kcp.buffer. Kcp ctor creates the buffer of size:
// (mtu + OVERHEAD) * 3 bytes.
// in other words, Encode only ever writes up to the above amount of bytes.
internal int Encode(byte[] ptr, int offset)
{
int previousPosition = offset;
offset += Utils.Encode32U(ptr, offset, conv);
offset += Utils.Encode8u(ptr, offset, (byte)cmd);
// IMPORTANT kcp encodes 'frg' as 1 byte.
// so we can only support up to 255 fragments.
// (which limits max message size to around 288 KB)
offset += Utils.Encode8u(ptr, offset, (byte)frg);
offset += Utils.Encode16U(ptr, offset, (ushort)wnd);
offset += Utils.Encode32U(ptr, offset, ts);
offset += Utils.Encode32U(ptr, offset, sn);
offset += Utils.Encode32U(ptr, offset, una);
offset += Utils.Encode32U(ptr, offset, (uint)data.Position);
int written = offset - previousPosition;
return written;
}
// reset to return a fresh segment to the pool
internal void Reset()
{
conv = 0;
cmd = 0;
frg = 0;
wnd = 0;
ts = 0;
sn = 0;
una = 0;
rto = 0;
xmit = 0;
resendts = 0;
fastack = 0;
// keep buffer for next pool usage, but reset length (= bytes written)
data.SetLength(0);
}
}
}

View File

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

View File

@@ -0,0 +1,76 @@
using System.Runtime.CompilerServices;
namespace kcp2k
{
public static partial class Utils
{
// Clamp so we don't have to depend on UnityEngine
public static int Clamp(int value, int min, int max)
{
if (value < min) return min;
if (value > max) return max;
return value;
}
// encode 8 bits unsigned int
public static int Encode8u(byte[] p, int offset, byte value)
{
p[0 + offset] = value;
return 1;
}
// decode 8 bits unsigned int
public static int Decode8u(byte[] p, int offset, out byte value)
{
value = p[0 + offset];
return 1;
}
// encode 16 bits unsigned int (lsb)
public static int Encode16U(byte[] p, int offset, ushort value)
{
p[0 + offset] = (byte)(value >> 0);
p[1 + offset] = (byte)(value >> 8);
return 2;
}
// decode 16 bits unsigned int (lsb)
public static int Decode16U(byte[] p, int offset, out ushort value)
{
ushort result = 0;
result |= p[0 + offset];
result |= (ushort)(p[1 + offset] << 8);
value = result;
return 2;
}
// encode 32 bits unsigned int (lsb)
public static int Encode32U(byte[] p, int offset, uint value)
{
p[0 + offset] = (byte)(value >> 0);
p[1 + offset] = (byte)(value >> 8);
p[2 + offset] = (byte)(value >> 16);
p[3 + offset] = (byte)(value >> 24);
return 4;
}
// decode 32 bits unsigned int (lsb)
public static int Decode32U(byte[] p, int offset, out uint value)
{
uint result = 0;
result |= p[0 + offset];
result |= (uint)(p[1 + offset] << 8);
result |= (uint)(p[2 + offset] << 16);
result |= (uint)(p[3 + offset] << 24);
value = result;
return 4;
}
// timediff was a macro in original Kcp. let's inline it if possible.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int TimeDiff(uint later, uint earlier)
{
return (int)(later - earlier);
}
}
}

View File

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

View File

@@ -1,16 +1,15 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;
using TEngine.Core;
using kcp2k;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
// ReSharper disable PossibleNullReferenceException
// ReSharper disable InconsistentNaming
#pragma warning disable CS8602
#pragma warning disable CS8625
#pragma warning disable CS8618
namespace TEngine.Core.Network
{
@@ -55,21 +54,22 @@ namespace TEngine.Core.Network
{
ThreadSynchronizationContext.Main.Post(OnConnectDisconnect);
}
_socket.Disconnect(false);
_socket.Close();
}
_kcp = null;
_maxSndWnd = 0;
_updateMinTime = 0;
_memoryPool.Dispose();
_memoryPool = null;
_sendAction = null;
_rawSendBuffer = null;
_rawReceiveBuffer = null;
_packetParser?.Dispose();
_receiveMemoryStream?.Dispose();
ClearConnectTimeout(ref _connectTimeoutId);
if (_messageCache != null)
@@ -77,13 +77,6 @@ namespace TEngine.Core.Network
_messageCache.Clear();
_messageCache = null;
}
if (_kcpIntPtr != IntPtr.Zero)
{
KCP.KcpRelease(_kcpIntPtr);
ConnectionPtrChannel.Remove(_kcpIntPtr);
_kcpIntPtr = IntPtr.Zero;
}
#if NETDEBUG
Log.Debug($"KCPClientNetwork ConnectionPtrChannel:{ConnectionPtrChannel.Count}");
#endif
@@ -110,6 +103,7 @@ namespace TEngine.Core.Network
_maxSndWnd = _kcpSettings.MaxSendWindowSize;
_messageCache = new Queue<MessageCacheInfo>();
_rawReceiveBuffer = new byte[_kcpSettings.Mtu + 5];
_memoryPool = MemoryPool<byte>.Shared;
_sendAction = (rpcId, routeTypeOpCode, routeId, memoryStream, message) =>
{
@@ -151,7 +145,7 @@ namespace TEngine.Core.Network
private Socket _socket;
private int _maxSndWnd;
private IntPtr _kcpIntPtr;
private Kcp _kcp;
private bool _isDisconnect;
private long _updateMinTime;
private byte[] _rawSendBuffer;
@@ -159,15 +153,12 @@ namespace TEngine.Core.Network
private byte[] _rawReceiveBuffer;
private KCPSettings _kcpSettings;
private APacketParser _packetParser;
private MemoryStream _receiveMemoryStream;
private MemoryPool<byte> _memoryPool;
private Queue<MessageCacheInfo> _messageCache;
private Action<uint, long, long, MemoryStream, object> _sendAction;
private readonly Queue<uint> _updateTimeOutTime = new Queue<uint>();
private EndPoint _clientEndPoint = new IPEndPoint(IPAddress.Any, 0);
private readonly SortedDictionary<uint, Action> _updateTimer = new SortedDictionary<uint, Action>();
private static readonly Dictionary<IntPtr, KCPClientNetwork> ConnectionPtrChannel = new Dictionary<IntPtr, KCPClientNetwork>();
private readonly SortedSet<uint> _updateTimer = new SortedSet<uint>();
private uint TimeNow => (uint) (TimeHelper.Now - _startTime);
private void Receive()
@@ -213,14 +204,11 @@ namespace TEngine.Core.Network
SendHeader(KcpHeader.ConfirmConnection);
ClearConnectTimeout(ref _connectTimeoutId);
// 创建KCP和相关的初始化
_kcpIntPtr = KCP.KcpCreate(channelId, new IntPtr(channelId));
KCP.KcpNodelay(_kcpIntPtr, 1, 5, 2, 1);
KCP.KcpWndsize(_kcpIntPtr, _kcpSettings.SendWindowSize, _kcpSettings.ReceiveWindowSize);
KCP.KcpSetmtu(_kcpIntPtr, _kcpSettings.Mtu);
KCP.KcpSetminrto(_kcpIntPtr, 30);
KCP.KcpSetoutput(_kcpIntPtr, KcpOutput);
_kcp = new Kcp(channelId, Output);
_kcp.SetNoDelay(1, 5, 2, true);
_kcp.SetWindowSize(_kcpSettings.SendWindowSize, _kcpSettings.ReceiveWindowSize);
_kcp.SetMtu(_kcpSettings.Mtu);
_rawSendBuffer = new byte[ushort.MaxValue];
_receiveMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream();
_packetParser = APacketParser.CreatePacketParser(NetworkTarget);
// 把缓存的消息全部发送给服务器
@@ -248,7 +236,6 @@ namespace TEngine.Core.Network
_messageCache.Clear();
_messageCache = null;
ConnectionPtrChannel.Add(_kcpIntPtr, this);
// 调用ChannelId改变事件、就算没有改变也要发下、接收事件的地方会判定下
ThreadSynchronizationContext.Main.Post(() =>
{
@@ -272,8 +259,8 @@ namespace TEngine.Core.Network
{
break;
}
KCP.KcpInput(_kcpIntPtr, _rawReceiveBuffer, 5, messageLength);
_kcp.Input(_rawReceiveBuffer, 5, messageLength);
AddToUpdate(0);
KcpReceive();
break;
@@ -284,7 +271,7 @@ namespace TEngine.Core.Network
{
break;
}
_isDisconnect = true;
Dispose();
break;
@@ -310,8 +297,8 @@ namespace TEngine.Core.Network
}
#endif
// 检查等待发送的消息如果超出两倍窗口大小KCP作者给的建议是要断开连接
var waitSendSize = KCP.KcpWaitsnd(_kcpIntPtr);
var waitSendSize = _kcp.WaitSnd;
if (waitSendSize > _maxSndWnd)
{
@@ -319,15 +306,9 @@ namespace TEngine.Core.Network
Dispose();
return;
}
// 发送消息
KCP.KcpSend(_kcpIntPtr, memoryStream.GetBuffer(), (int) memoryStream.Length);
// 因为memoryStream对象池出来的、所以需要手动回收下
_kcp.Send(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
memoryStream.Dispose();
AddToUpdate(0);
}
@@ -365,7 +346,7 @@ namespace TEngine.Core.Network
_sendAction(rpcId, routeTypeOpCode, entityId, null, message);
}
private void Output(IntPtr bytes, int count)
private void Output(byte[] bytes, int count)
{
#if TENGINE_DEVELOP
if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId)
@@ -374,7 +355,7 @@ namespace TEngine.Core.Network
return;
}
#endif
if (IsDisposed || _kcpIntPtr == IntPtr.Zero)
if (IsDisposed)
{
return;
}
@@ -388,7 +369,7 @@ namespace TEngine.Core.Network
_rawSendBuffer.WriteTo(0, (byte) KcpHeader.ReceiveData);
_rawSendBuffer.WriteTo(1, ChannelId);
Marshal.Copy(bytes, _rawSendBuffer, 5, count);
Buffer.BlockCopy(bytes, 0, _rawSendBuffer, 5, count);
_socket.Send(_rawSendBuffer, 0, count + 5, SocketFlags.None);
}
catch (Exception e)
@@ -406,7 +387,7 @@ namespace TEngine.Core.Network
return;
}
#endif
if (IsDisposed || _kcpIntPtr == IntPtr.Zero)
if (IsDisposed)
{
return;
}
@@ -416,8 +397,8 @@ namespace TEngine.Core.Network
try
{
// 获得一个完整消息的长度
var peekSize = KCP.KcpPeeksize(_kcpIntPtr);
var peekSize = _kcp.PeekSize();
// 如果没有接收的消息那就跳出当前循环。
@@ -433,9 +414,8 @@ namespace TEngine.Core.Network
throw new Exception("SocketError.NetworkReset");
}
_receiveMemoryStream.SetLength(peekSize);
_receiveMemoryStream.Seek(0, SeekOrigin.Begin);
var receiveCount = KCP.KcpRecv(_kcpIntPtr, _receiveMemoryStream.GetBuffer(), peekSize);
var receiveMemoryOwner = _memoryPool.Rent(Packet.OuterPacketMaxLength);
var receiveCount = _kcp.Receive(receiveMemoryOwner.Memory, peekSize);
// 如果接收的长度跟peekSize不一样不需要处理因为消息肯定有问题的(虽然不可能出现)。
@@ -445,13 +425,10 @@ namespace TEngine.Core.Network
break;
}
var packInfo = _packetParser.UnPack(_receiveMemoryStream);
if (packInfo == null)
if (!_packetParser.UnPack(receiveMemoryOwner, out var packInfo))
{
break;
}
ThreadSynchronizationContext.Main.Post(() =>
{
@@ -483,15 +460,13 @@ namespace TEngine.Core.Network
foreach (var timeId in _updateTimer)
{
var key = timeId.Key;
if (key > nowTime)
if (timeId > nowTime)
{
_updateMinTime = key;
_updateMinTime = timeId;
break;
}
_updateTimeOutTime.Enqueue(key);
_updateTimeOutTime.Enqueue(timeId);
}
while (_updateTimeOutTime.TryDequeue(out var time))
@@ -521,7 +496,7 @@ namespace TEngine.Core.Network
_updateMinTime = tillTime;
}
_updateTimer[tillTime] = KcpUpdate;
_updateTimer.Add(tillTime);
}
private void KcpUpdate()
@@ -537,17 +512,14 @@ namespace TEngine.Core.Network
try
{
KCP.KcpUpdate(_kcpIntPtr, nowTime);
_kcp.Update(nowTime);
}
catch (Exception e)
{
Log.Error(e);
}
if (_kcpIntPtr != IntPtr.Zero)
{
AddToUpdate(KCP.KcpCheck(_kcpIntPtr, nowTime));
}
AddToUpdate(_kcp.Check(nowTime));
}
public override void RemoveChannel(uint channelId)
@@ -590,38 +562,6 @@ namespace TEngine.Core.Network
TimerScheduler.Instance.Core.RemoveByRef(ref connectTimeoutId);
}
#if ENABLE_IL2CPP
[AOT.MonoPInvokeCallback(typeof(KcpOutput))]
#endif
private static int KcpOutput(IntPtr bytes, int count, IntPtr kcp, IntPtr user)
{
#if TENGINE_DEVELOP
if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId)
{
Log.Error("not in NetworkThread!");
return 0;
}
#endif
try
{
if (kcp == IntPtr.Zero || !ConnectionPtrChannel.TryGetValue(kcp, out var channel))
{
return 0;
}
if (!channel.IsDisposed)
{
channel.Output(bytes, count);
}
}
catch (Exception e)
{
Log.Error(e);
}
return count;
}
#endregion
}

View File

@@ -5,11 +5,10 @@ using System.Linq;
using System.Net;
using System.Net.Sockets;
using TEngine.DataStructure;
using TEngine.Core;
using kcp2k;
// ReSharper disable InconsistentNaming
#pragma warning disable CS8601
#pragma warning disable CS8625
#pragma warning disable CS8618
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
namespace TEngine.Core.Network
{
@@ -102,7 +101,6 @@ namespace TEngine.Core.Network
private readonly SortedOneToManyList<uint, uint> _pendingConnectionTimer = new SortedOneToManyList<uint, uint>();
private readonly Dictionary<uint, KCPServerNetworkChannel> _pendingConnection = new Dictionary<uint, KCPServerNetworkChannel>();
private readonly Dictionary<uint, KCPServerNetworkChannel> _connectionChannel = new Dictionary<uint, KCPServerNetworkChannel>();
public static readonly Dictionary<IntPtr, KCPServerNetworkChannel> ConnectionPtrChannel = new Dictionary<IntPtr, KCPServerNetworkChannel>();
private KCPSettings KcpSettings { get; set; }
private uint TimeNow => (uint) (TimeHelper.Now - _startTime);
@@ -170,6 +168,7 @@ namespace TEngine.Core.Network
try
{
var receiveLength = _socket.ReceiveFrom(_rawReceiveBuffer, ref _clientEndPoint);
if (receiveLength < 1)
{
continue;
@@ -182,7 +181,6 @@ namespace TEngine.Core.Network
{
case KcpHeader.RequestConnection:
{
// Log.Debug("KcpHeader.RequestConnection");
if (receiveLength != 5)
{
break;
@@ -232,17 +230,12 @@ namespace TEngine.Core.Network
break;
}
var kcpIntPtr = KCP.KcpCreate(channelId, new IntPtr(channelId));
KCP.KcpNodelay(kcpIntPtr, 1, 5, 2, 1);
KCP.KcpWndsize(kcpIntPtr, KcpSettings.SendWindowSize, KcpSettings.ReceiveWindowSize);
KCP.KcpSetmtu(kcpIntPtr, KcpSettings.Mtu);
KCP.KcpSetminrto(kcpIntPtr, 30);
KCP.KcpSetoutput(kcpIntPtr, KcpOutput);
var kcp = new Kcp(channelId, channel.Output);
kcp.SetNoDelay(1, 5, 2, true);
kcp.SetWindowSize(KcpSettings.SendWindowSize, KcpSettings.ReceiveWindowSize);
kcp.SetMtu(KcpSettings.Mtu);
_connectionChannel.Add(channel.Id, channel);
ConnectionPtrChannel.Add(kcpIntPtr, channel);
channel.Connect(kcpIntPtr, AddToUpdate, KcpSettings.MaxSendWindowSize, NetworkTarget, NetworkMessageScheduler);
channel.Connect(kcp, AddToUpdate, KcpSettings.MaxSendWindowSize, NetworkTarget, NetworkMessageScheduler);
break;
}
case KcpHeader.ReceiveData:
@@ -260,7 +253,7 @@ namespace TEngine.Core.Network
break;
}
KCP.KcpInput(channel.KcpIntPtr, _rawReceiveBuffer, 5, messageLength);
channel.Kcp.Input(_rawReceiveBuffer, 5, messageLength);
AddToUpdate(0, channel.Id);
channel.Receive();
break;
@@ -279,6 +272,7 @@ namespace TEngine.Core.Network
}
}
}
private bool RemovePendingConnection(uint channelId, EndPoint remoteEndPoint, out KCPServerNetworkChannel channel)
{
@@ -391,18 +385,19 @@ namespace TEngine.Core.Network
continue;
}
var channelKcp = channel.Kcp;
try
{
KCP.KcpUpdate(channel.KcpIntPtr, nowTime);
channelKcp.Update(nowTime);
}
catch (Exception e)
{
Log.Error(e);
}
if (channel.KcpIntPtr != IntPtr.Zero)
if (channelKcp != null)
{
AddToUpdate(KCP.KcpCheck(channel.KcpIntPtr, nowTime), channelId);
AddToUpdate(channelKcp.Check(nowTime), channelId);
}
}
@@ -461,39 +456,6 @@ namespace TEngine.Core.Network
_updateTimer.Add(tillTime, channelId);
}
#if ENABLE_IL2CPP
[AOT.MonoPInvokeCallback(typeof(KcpOutput))]
#endif
private static int KcpOutput(IntPtr bytes, int count, IntPtr kcp, IntPtr user)
{
#if TENGINE_DEVELOP
if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId)
{
Log.Error("not in NetworkThread!");
return 0;
}
#endif
try
{
if (kcp == IntPtr.Zero || !ConnectionPtrChannel.TryGetValue(kcp, out var channel))
{
return 0;
}
if (!channel.IsDisposed)
{
channel.Output(bytes, count);
}
}
catch (Exception e)
{
Log.Error(e);
}
return count;
}
#endregion
}

View File

@@ -1,12 +1,14 @@
using System;
using System.Buffers;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using TEngine.Core;
using TEngine.DataStructure;
using kcp2k;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
// ReSharper disable InconsistentNaming
#pragma warning disable CS8625
#pragma warning disable CS8618
namespace TEngine.Core.Network
{
@@ -19,8 +21,8 @@ namespace TEngine.Core.Network
public readonly uint CreateTime;
private readonly Socket _socket;
private Action<uint, uint> _addToUpdate;
private MemoryStream _receiveMemoryStream;
public IntPtr KcpIntPtr { get; private set; }
private MemoryPool<byte> _memoryPool;
public Kcp Kcp { get; private set; }
public override event Action OnDispose;
public override event Action<APackInfo> OnReceiveMemoryStream;
@@ -37,6 +39,7 @@ namespace TEngine.Core.Network
_socket = socket;
CreateTime = createTime;
RemoteEndPoint = remoteEndPoint;
_memoryPool = MemoryPool<byte>.Shared;
}
public override void Dispose()
@@ -53,28 +56,23 @@ namespace TEngine.Core.Network
return;
}
Kcp = null;
var buff = new byte[5];
buff.WriteTo(0, (byte) KcpHeader.Disconnect);
buff.WriteTo(1, Id);
_socket.SendTo(buff, 5, SocketFlags.None, RemoteEndPoint);
if (KcpIntPtr != IntPtr.Zero)
{
KCPServerNetwork.ConnectionPtrChannel.Remove(KcpIntPtr);
KCP.KcpRelease(KcpIntPtr);
KcpIntPtr = IntPtr.Zero;
}
#if NETDEBUG
Log.Debug($"KCPServerNetworkChannel ConnectionPtrChannel:{KCPServerNetwork.ConnectionPtrChannel.Count}");
#endif
_maxSndWnd = 0;
_addToUpdate = null;
_receiveMemoryStream?.Dispose();
_memoryPool.Dispose();
_memoryPool = null;
ThreadSynchronizationContext.Main.Post(OnDispose);
base.Dispose();
}
public void Connect(IntPtr kcpIntPtr, Action<uint, uint> addToUpdate, int maxSndWnd, NetworkTarget networkTarget, ANetworkMessageScheduler networkMessageScheduler)
public void Connect(Kcp kcp, Action<uint, uint> addToUpdate, int maxSndWnd, NetworkTarget networkTarget, ANetworkMessageScheduler networkMessageScheduler)
{
#if TENGINE_DEVELOP
if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId)
@@ -83,11 +81,10 @@ namespace TEngine.Core.Network
return;
}
#endif
KcpIntPtr = kcpIntPtr;
Kcp = kcp;
_maxSndWnd = maxSndWnd;
_addToUpdate = addToUpdate;
_rawSendBuffer = new byte[ushort.MaxValue];
_receiveMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream();
PacketParser = APacketParser.CreatePacketParser(networkTarget);
ThreadSynchronizationContext.Main.Post(() =>
@@ -110,14 +107,14 @@ namespace TEngine.Core.Network
return;
}
#endif
if (IsDisposed || KcpIntPtr == IntPtr.Zero)
if (IsDisposed)
{
return;
}
// 检查等待发送的消息如果超出两倍窗口大小KCP作者给的建议是要断开连接
var waitSendSize = KCP.KcpWaitsnd(KcpIntPtr);
var waitSendSize = Kcp.WaitSnd;
if (waitSendSize > _maxSndWnd)
{
@@ -125,15 +122,10 @@ namespace TEngine.Core.Network
Dispose();
return;
}
// 发送消息
KCP.KcpSend(KcpIntPtr, memoryStream.GetBuffer(), (int) memoryStream.Length);
Kcp.Send(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
// 因为memoryStream对象池出来的、所以需要手动回收下
memoryStream.Dispose();
_addToUpdate(0, Id);
}
@@ -146,7 +138,7 @@ namespace TEngine.Core.Network
return;
}
#endif
if (IsDisposed || KcpIntPtr == IntPtr.Zero)
if (IsDisposed)
{
return;
}
@@ -155,32 +147,25 @@ namespace TEngine.Core.Network
{
try
{
if (KcpIntPtr == IntPtr.Zero)
{
return;
}
// 获得一个完整消息的长度
var peekSize = KCP.KcpPeeksize(KcpIntPtr);
var peekSize = Kcp.PeekSize();
// 如果没有接收的消息那就跳出当前循环。
if (peekSize < 0)
{
return;
}
// 如果为0表示当前消息发生错误。提示下、一般情况不会发生的
if (peekSize == 0)
{
throw new Exception("SocketError.NetworkReset");
}
_receiveMemoryStream.SetLength(peekSize);
_receiveMemoryStream.Seek(0, SeekOrigin.Begin);
var receiveCount = KCP.KcpRecv(KcpIntPtr, _receiveMemoryStream.GetBuffer(), peekSize);
var receiveMemoryOwner = _memoryPool.Rent(Packet.OuterPacketMaxLength);
var receiveCount = Kcp.Receive(receiveMemoryOwner.Memory, peekSize);
// 如果接收的长度跟peekSize不一样不需要处理因为消息肯定有问题的(虽然不可能出现)。
@@ -190,9 +175,7 @@ namespace TEngine.Core.Network
break;
}
var packInfo = PacketParser.UnPack(_receiveMemoryStream);
if (packInfo == null)
if (!PacketParser.UnPack(receiveMemoryOwner,out var packInfo))
{
break;
}
@@ -215,7 +198,7 @@ namespace TEngine.Core.Network
}
}
public void Output(IntPtr bytes, int count)
public void Output(byte[] bytes, int count)
{
#if TENGINE_DEVELOP
if (NetworkThread.Instance.ManagedThreadId != Thread.CurrentThread.ManagedThreadId)
@@ -224,7 +207,7 @@ namespace TEngine.Core.Network
return;
}
#endif
if (IsDisposed || KcpIntPtr == IntPtr.Zero)
if (IsDisposed)
{
return;
}
@@ -238,7 +221,7 @@ namespace TEngine.Core.Network
_rawSendBuffer.WriteTo(0, (byte) KcpHeader.ReceiveData);
_rawSendBuffer.WriteTo(1, Id);
Marshal.Copy(bytes, _rawSendBuffer, 5, count);
Buffer.BlockCopy(bytes, 0, _rawSendBuffer, 5, count);
_socket.SendTo(_rawSendBuffer, 0, count + 5, SocketFlags.None, RemoteEndPoint);
}
catch (Exception e)

View File

@@ -41,6 +41,7 @@ namespace TEngine.Core.Network
_isInit = true;
OnConnectFail = onConnectFail;
OnConnectComplete = onConnectComplete;
OnConnectDisconnect = onConnectDisconnect;
ChannelId = 0xC0000000 | (uint) new Random().Next();
_sendAction = (rpcId, routeTypeOpCode, routeId, memoryStream, message) =>

View File

@@ -1,37 +1,27 @@
#if TENGINE_NET
using System.Buffers;
using TEngine.DataStructure;
using TEngine.Core;
#pragma warning disable CS8600
#pragma warning disable CS8625
#pragma warning disable CS8603
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
namespace TEngine.Core.Network;
public sealed class InnerPackInfo : APackInfo
{
public static InnerPackInfo Create()
public static InnerPackInfo Create(IMemoryOwner<byte> memoryOwner)
{
return Pool<InnerPackInfo>.Rent();
}
public static InnerPackInfo Create(uint rpcId, long routeId, uint protocolCode)
{
var innerPackInfo = Pool<InnerPackInfo>.Rent();
innerPackInfo.RpcId = rpcId;
innerPackInfo.RouteId = routeId;
innerPackInfo.ProtocolCode = protocolCode;
var innerPackInfo = Rent<InnerPackInfo>();
innerPackInfo.MemoryOwner = memoryOwner;
return innerPackInfo;
}
public static InnerPackInfo Create(uint rpcId, long routeId, uint protocolCode, long routeTypeCode, MemoryStream memoryStream)
public override MemoryStream CreateMemoryStream()
{
var innerPackInfo = Pool<InnerPackInfo>.Rent();
innerPackInfo.RpcId = rpcId;
innerPackInfo.RouteId = routeId;
innerPackInfo.ProtocolCode = protocolCode;
innerPackInfo.RouteTypeCode = routeTypeCode;
innerPackInfo.MemoryStream = memoryStream;
return innerPackInfo;
var recyclableMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream();
recyclableMemoryStream.Write(MemoryOwner.Memory.Span.Slice(0, Packet.InnerPacketHeadLength + MessagePacketLength));
recyclableMemoryStream.Seek(0, SeekOrigin.Begin);
return recyclableMemoryStream;
}
public override void Dispose()
@@ -42,38 +32,36 @@ public sealed class InnerPackInfo : APackInfo
public override object Deserialize(Type messageType)
{
using (MemoryStream)
var memoryOwnerMemory = MemoryOwner.Memory;
memoryOwnerMemory = memoryOwnerMemory.Slice(Packet.InnerPacketHeadLength, MessagePacketLength);
switch (ProtocolCode)
{
MemoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin);
switch (ProtocolCode)
case >= Opcode.InnerBsonRouteResponse:
{
case >= Opcode.InnerBsonRouteResponse:
{
return MongoHelper.Instance.DeserializeFrom(messageType, MemoryStream);
}
case >= Opcode.InnerRouteResponse:
{
return ProtoBufHelper.FromStream(messageType, MemoryStream);
}
case >= Opcode.OuterRouteResponse:
{
return ProtoBufHelper.FromStream(messageType, MemoryStream);
}
case >= Opcode.InnerBsonRouteMessage:
{
return MongoHelper.Instance.DeserializeFrom(messageType, MemoryStream);
}
case >= Opcode.InnerRouteMessage:
case >= Opcode.OuterRouteMessage:
{
return ProtoBufHelper.FromStream(messageType, MemoryStream);
}
default:
{
Log.Error($"protocolCode:{ProtocolCode} Does not support processing protocol");
return null;
}
return MongoHelper.Instance.Deserialize(memoryOwnerMemory, messageType);
}
case >= Opcode.InnerRouteResponse:
{
return ProtoBufHelper.FromMemory(messageType, memoryOwnerMemory);
}
case >= Opcode.OuterRouteResponse:
{
return ProtoBufHelper.FromMemory(messageType, memoryOwnerMemory);
}
case >= Opcode.InnerBsonRouteMessage:
{
return MongoHelper.Instance.Deserialize(memoryOwnerMemory, messageType);
}
case >= Opcode.InnerRouteMessage:
case >= Opcode.OuterRouteMessage:
{
return ProtoBufHelper.FromMemory(messageType, memoryOwnerMemory);
}
default:
{
Log.Error($"protocolCode:{ProtocolCode} Does not support processing protocol");
return null;
}
}
}
@@ -88,10 +76,15 @@ public sealed class InnerPacketParser : APacketParser
private bool _isUnPackHead = true;
private readonly byte[] _messageHead = new byte[Packet.InnerPacketHeadLength];
public InnerPacketParser()
{
MemoryPool = MemoryPool<byte>.Shared;
}
public override bool UnPack(CircularBuffer buffer, out APackInfo packInfo)
{
packInfo = null;
while (!IsDisposed)
{
if (_isUnPackHead)
@@ -100,48 +93,40 @@ public sealed class InnerPacketParser : APacketParser
{
return false;
}
_ = buffer.Read(_messageHead, 0, Packet.InnerPacketHeadLength);
_messagePacketLength = BitConverter.ToInt32(_messageHead, 0);
if (_messagePacketLength > Packet.PacketBodyMaxLength)
{
throw new ScanException($"The received information exceeds the maximum limit = {_messagePacketLength}");
}
_protocolCode = BitConverter.ToUInt32(_messageHead, Packet.PacketLength);
_rpcId = BitConverter.ToUInt32(_messageHead, Packet.OuterPacketRpcIdLocation);
_rpcId = BitConverter.ToUInt32(_messageHead, Packet.InnerPacketRpcIdLocation);
_routeId = BitConverter.ToInt64(_messageHead, Packet.InnerPacketRouteRouteIdLocation);
_isUnPackHead = false;
}
try
{
if (buffer.Length < _messagePacketLength)
{
return false;
}
_isUnPackHead = true;
packInfo = InnerPackInfo.Create(_rpcId, _routeId, _protocolCode);
if (_messagePacketLength > 0)
{
return true;
}
var memoryStream = MemoryStreamHelper.GetRecyclableMemoryStream();
// 创建消息包
var memoryOwner = MemoryPool.Rent(Packet.InnerPacketMaxLength);
packInfo = InnerPackInfo.Create(memoryOwner);
packInfo.RpcId = _rpcId;
packInfo.RouteId = _routeId;
packInfo.ProtocolCode = _protocolCode;
packInfo.MessagePacketLength = _messagePacketLength;
// 写入消息体的信息到内存中
memoryStream.Seek(Packet.InnerPacketHeadLength, SeekOrigin.Begin);
buffer.Read(memoryStream, _messagePacketLength);
buffer.Read(memoryOwner.Memory.Slice(Packet.InnerPacketHeadLength), _messagePacketLength);
// 写入消息头的信息到内存中
memoryStream.Seek(0, SeekOrigin.Begin);
memoryStream.Write(BitConverter.GetBytes(_messagePacketLength));
memoryStream.Write(BitConverter.GetBytes(packInfo.ProtocolCode));
memoryStream.Write(BitConverter.GetBytes(packInfo.RpcId));
memoryStream.Write(BitConverter.GetBytes(packInfo.RouteId));
memoryStream.Seek(0, SeekOrigin.Begin);
packInfo.MemoryStream = memoryStream;
_messageHead.AsMemory().CopyTo( memoryOwner.Memory.Slice(0, Packet.InnerPacketHeadLength));
return true;
}
catch (Exception e)
@@ -151,69 +136,50 @@ public sealed class InnerPacketParser : APacketParser
return false;
}
}
return false;
}
public override APackInfo UnPack(MemoryStream memoryStream)
public override bool UnPack(IMemoryOwner<byte> memoryOwner, out APackInfo packInfo)
{
InnerPackInfo packInfo = null;
packInfo = null;
try
{
if (memoryStream == null || memoryStream.Length < Packet.InnerPacketHeadLength)
{
return null;
}
_ = memoryStream.Read(_messageHead, 0, Packet.InnerPacketHeadLength);
_messagePacketLength = BitConverter.ToInt32(_messageHead, 0);
var memorySpan = memoryOwner.Memory.Span;
if (memorySpan.Length < Packet.InnerPacketHeadLength)
{
return false;
}
_messagePacketLength = BitConverter.ToInt32(memorySpan);
if (_messagePacketLength > Packet.PacketBodyMaxLength)
{
throw new ScanException($"The received information exceeds the maximum limit = {_messagePacketLength}");
}
packInfo = InnerPackInfo.Create();
packInfo.ProtocolCode = BitConverter.ToUInt32(_messageHead, Packet.PacketLength);
packInfo.RpcId = BitConverter.ToUInt32(_messageHead, Packet.OuterPacketRpcIdLocation);
packInfo.RouteId = BitConverter.ToInt64(_messageHead, Packet.InnerPacketRouteRouteIdLocation);
if (memoryStream.Length < _messagePacketLength)
{
return null;
}
if (_messagePacketLength <= 0)
{
return packInfo;
}
var outMemoryStream = MemoryStreamHelper.GetRecyclableMemoryStream();
memoryStream.WriteTo(outMemoryStream);
outMemoryStream.Seek(0, SeekOrigin.Begin);
packInfo.MemoryStream = outMemoryStream;
return packInfo;
packInfo = InnerPackInfo.Create(memoryOwner);
packInfo.MessagePacketLength = _messagePacketLength;
packInfo.ProtocolCode = BitConverter.ToUInt32(memorySpan[Packet.PacketLength..]);
packInfo.RpcId = BitConverter.ToUInt32(memorySpan[Packet.OuterPacketRpcIdLocation..]);
packInfo.RouteId = BitConverter.ToInt64(memorySpan[Packet.InnerPacketRouteRouteIdLocation..]);
if (memorySpan.Length < _messagePacketLength)
{
return false;
}
return _messagePacketLength >= 0;
}
catch (Exception e)
{
packInfo?.Dispose();
Log.Error(e);
return null;
Console.WriteLine(e);
throw;
}
}
public static void Serialize(object message, MemoryStream stream)
{
if (message is IBsonMessage)
{
MongoHelper.Instance.SerializeTo(message, stream);
return;
}
ProtoBufHelper.ToStream(message, stream);
}
public static MemoryStream Pack(uint rpcId, long routeId, MemoryStream memoryStream)
{
memoryStream.Seek(Packet.InnerPacketRpcIdLocation, SeekOrigin.Begin);
@@ -233,7 +199,24 @@ public sealed class InnerPacketParser : APacketParser
if (message != null)
{
Serialize(message, memoryStream);
if (message is IBsonMessage)
{
try
{
MongoHelper.Instance.SerializeTo(message, memoryStream);
}
catch (Exception e)
{
Log.Fatal(e);
throw;
}
}
else
{
ProtoBufHelper.ToStream(message, memoryStream);
}
opCode = MessageDispatcherSystem.Instance.GetOpCode(message.GetType());
packetBodyCount = (int)(memoryStream.Position - Packet.InnerPacketHeadLength);
}
@@ -255,7 +238,6 @@ public sealed class InnerPacketParser : APacketParser
public override void Dispose()
{
_messagePacketLength = 0;
Array.Clear(_messageHead, 0, _messageHead.Length);
base.Dispose();
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Buffers;
using System.IO;
#pragma warning disable CS8625
#pragma warning disable CS8618
namespace TEngine.Core.Network
{
@@ -11,17 +10,34 @@ namespace TEngine.Core.Network
public long RouteId;
public uint ProtocolCode;
public long RouteTypeCode;
public MemoryStream MemoryStream;
public int MessagePacketLength;
public IMemoryOwner<byte> MemoryOwner;
public bool IsDisposed;
public static T Rent<T>() where T : APackInfo
{
var aPackInfo = Pool<T>.Rent();
aPackInfo.IsDisposed = false;
return aPackInfo;
}
public abstract object Deserialize(Type messageType);
public abstract MemoryStream CreateMemoryStream();
public virtual void Dispose()
{
if (IsDisposed)
{
return;
}
RpcId = 0;
RouteId = 0;
ProtocolCode = 0;
RouteTypeCode = 0;
MemoryStream = null;
MessagePacketLength = 0;
MemoryOwner.Dispose();
MemoryOwner = null;
IsDisposed = true;
}
}
}

View File

@@ -1,12 +1,13 @@
using System;
using System.Buffers;
using System.IO;
using TEngine.DataStructure;
using TEngine.Core;
namespace TEngine.Core.Network
{
public abstract class APacketParser : IDisposable
{
protected MemoryPool<byte> MemoryPool;
protected bool IsDisposed { get; private set; }
public static APacketParser CreatePacketParser(NetworkTarget networkTarget)
@@ -17,8 +18,8 @@ namespace TEngine.Core.Network
{
#if TENGINE_NET
return new InnerPacketParser();
#else
return null;
#else
throw new NotSupportedException($"PacketParserHelper Create NotSupport {networkTarget}");
#endif
}
case NetworkTarget.Outer:
@@ -33,11 +34,11 @@ namespace TEngine.Core.Network
}
public abstract bool UnPack(CircularBuffer buffer, out APackInfo packInfo);
public abstract APackInfo UnPack(MemoryStream memoryStream);
public abstract bool UnPack(IMemoryOwner<byte> memoryOwner, out APackInfo packInfo);
public virtual void Dispose()
{
IsDisposed = true;
MemoryPool.Dispose();
}
}
}

Some files were not shown because too many files have changed in this diff Show More