mirror of
https://github.com/Alex-Rachel/TEngine.git
synced 2025-08-14 16:51:28 +00:00
[+] 接入ET8服务端
[+] 接入ET8服务端
This commit is contained in:
141
Assets/GameScripts/DotNet/Core/World/ActorId.cs
Normal file
141
Assets/GameScripts/DotNet/Core/World/ActorId.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using MemoryPack;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
[MemoryPackable]
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public partial struct Address
|
||||
{
|
||||
[MemoryPackOrder(0)]
|
||||
public int Process;
|
||||
[MemoryPackOrder(1)]
|
||||
public int Fiber;
|
||||
|
||||
public bool Equals(Address other)
|
||||
{
|
||||
return this.Process == other.Process && this.Fiber == other.Fiber;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Address other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(this.Process, this.Fiber);
|
||||
}
|
||||
|
||||
public Address(int process, int fiber)
|
||||
{
|
||||
this.Process = process;
|
||||
this.Fiber = fiber;
|
||||
}
|
||||
|
||||
public static bool operator ==(Address left, Address right)
|
||||
{
|
||||
return left.Process == right.Process && left.Fiber == right.Fiber;
|
||||
}
|
||||
|
||||
public static bool operator !=(Address left, Address right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Process}:{this.Fiber}";
|
||||
}
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public partial struct ActorId
|
||||
{
|
||||
public bool Equals(ActorId other)
|
||||
{
|
||||
return this.Address == other.Address && this.InstanceId == other.InstanceId;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ActorId other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(this.Address, this.InstanceId);
|
||||
}
|
||||
|
||||
[MemoryPackOrder(0)]
|
||||
public Address Address;
|
||||
[MemoryPackOrder(1)]
|
||||
public long InstanceId;
|
||||
|
||||
[BsonIgnore]
|
||||
public int Process
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Address.Process;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.Address.Process = value;
|
||||
}
|
||||
}
|
||||
|
||||
[BsonIgnore]
|
||||
public int Fiber
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Address.Fiber;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.Address.Fiber = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ActorId(int process, int fiber)
|
||||
{
|
||||
this.Address = new Address(process, fiber);
|
||||
this.InstanceId = 1;
|
||||
}
|
||||
|
||||
public ActorId(int process, int fiber, long instanceId)
|
||||
{
|
||||
this.Address = new Address(process, fiber);
|
||||
this.InstanceId = instanceId;
|
||||
}
|
||||
|
||||
public ActorId(Address address): this(address, 1)
|
||||
{
|
||||
}
|
||||
|
||||
public ActorId(Address address, long instanceId)
|
||||
{
|
||||
this.Address = address;
|
||||
this.InstanceId = instanceId;
|
||||
}
|
||||
|
||||
public static bool operator ==(ActorId left, ActorId right)
|
||||
{
|
||||
return left.InstanceId == right.InstanceId && left.Address == right.Address;
|
||||
}
|
||||
|
||||
public static bool operator !=(ActorId left, ActorId right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{this.Process}:{this.Fiber}:{this.InstanceId}";
|
||||
}
|
||||
}
|
||||
}
|
3
Assets/GameScripts/DotNet/Core/World/ActorId.cs.meta
Normal file
3
Assets/GameScripts/DotNet/Core/World/ActorId.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e8f0bd4aa4045ddb59117f81ff62be5
|
||||
timeCreated: 1687104833
|
36
Assets/GameScripts/DotNet/Core/World/IMessage.cs
Normal file
36
Assets/GameScripts/DotNet/Core/World/IMessage.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
namespace ET
|
||||
{
|
||||
public interface IMessage
|
||||
{
|
||||
}
|
||||
|
||||
public interface IRequest: IMessage
|
||||
{
|
||||
int RpcId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IResponse: IMessage
|
||||
{
|
||||
int Error
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
string Message
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
int RpcId
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/GameScripts/DotNet/Core/World/IMessage.cs.meta
Normal file
11
Assets/GameScripts/DotNet/Core/World/IMessage.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21e594d3ffbf71b45ad9577e4c586518
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
22
Assets/GameScripts/DotNet/Core/World/ISingletonAwake.cs
Normal file
22
Assets/GameScripts/DotNet/Core/World/ISingletonAwake.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace ET
|
||||
{
|
||||
public interface ISingletonAwake
|
||||
{
|
||||
void Awake();
|
||||
}
|
||||
|
||||
public interface ISingletonAwake<A>
|
||||
{
|
||||
void Awake(A a);
|
||||
}
|
||||
|
||||
public interface ISingletonAwake<A, B>
|
||||
{
|
||||
void Awake(A a, B b);
|
||||
}
|
||||
|
||||
public interface ISingletonAwake<A, B, C>
|
||||
{
|
||||
void Awake(A a, B b, C c);
|
||||
}
|
||||
}
|
11
Assets/GameScripts/DotNet/Core/World/ISingletonAwake.cs.meta
Normal file
11
Assets/GameScripts/DotNet/Core/World/ISingletonAwake.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cc34700016e57b43981e910cb9fc129
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,7 @@
|
||||
namespace ET
|
||||
{
|
||||
public interface ISingletonDestroy
|
||||
{
|
||||
void Destroy();
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05613b4e63ae5a14887a34e97a0c1a0f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
7
Assets/GameScripts/DotNet/Core/World/ISingletonLoad.cs
Normal file
7
Assets/GameScripts/DotNet/Core/World/ISingletonLoad.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace ET
|
||||
{
|
||||
public interface ISingletonLoad
|
||||
{
|
||||
void Load();
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a6552160d3164dee967d0dc9db1611a3
|
||||
timeCreated: 1687250763
|
8
Assets/GameScripts/DotNet/Core/World/Module.meta
Normal file
8
Assets/GameScripts/DotNet/Core/World/Module.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 565753fb8d09f4c40903cc0071a3461f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/GameScripts/DotNet/Core/World/Module/Actor.meta
Normal file
8
Assets/GameScripts/DotNet/Core/World/Module/Actor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 395a79eb78315e84e92fde7209c84fda
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public class ActorMessageDispatcherInfo
|
||||
{
|
||||
public SceneType SceneType { get; }
|
||||
|
||||
public IMActorHandler IMActorHandler { get; }
|
||||
|
||||
public ActorMessageDispatcherInfo(SceneType sceneType, IMActorHandler imActorHandler)
|
||||
{
|
||||
this.SceneType = sceneType;
|
||||
this.IMActorHandler = imActorHandler;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actor消息分发组件
|
||||
/// </summary>
|
||||
public class ActorMessageDispatcherComponent: SingletonLock<ActorMessageDispatcherComponent>, ISingletonAwake
|
||||
{
|
||||
private readonly Dictionary<Type, List<ActorMessageDispatcherInfo>> ActorMessageHandlers = new();
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
HashSet<Type> types = EventSystem.Instance.GetTypes(typeof (ActorMessageHandlerAttribute));
|
||||
|
||||
foreach (Type type in types)
|
||||
{
|
||||
this.Register(type);
|
||||
}
|
||||
|
||||
HashSet<Type> types2 = EventSystem.Instance.GetTypes(typeof (ActorMessageLocationHandlerAttribute));
|
||||
|
||||
foreach (Type type in types2)
|
||||
{
|
||||
this.Register(type);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
World.Instance.AddSingleton<ActorMessageDispatcherComponent>(true);
|
||||
}
|
||||
|
||||
private void Register(Type type)
|
||||
{
|
||||
object obj = Activator.CreateInstance(type);
|
||||
|
||||
IMActorHandler imHandler = obj as IMActorHandler;
|
||||
if (imHandler == null)
|
||||
{
|
||||
throw new Exception($"message handler not inherit IMActorHandler abstract class: {obj.GetType().FullName}");
|
||||
}
|
||||
|
||||
object[] attrs = type.GetCustomAttributes(typeof(ActorMessageHandlerAttribute), true);
|
||||
|
||||
foreach (object attr in attrs)
|
||||
{
|
||||
ActorMessageHandlerAttribute actorMessageHandlerAttribute = attr as ActorMessageHandlerAttribute;
|
||||
|
||||
Type messageType = imHandler.GetRequestType();
|
||||
|
||||
Type handleResponseType = imHandler.GetResponseType();
|
||||
if (handleResponseType != null)
|
||||
{
|
||||
Type responseType = OpcodeType.Instance.GetResponseType(messageType);
|
||||
if (handleResponseType != responseType)
|
||||
{
|
||||
throw new Exception($"message handler response type error: {messageType.FullName}");
|
||||
}
|
||||
}
|
||||
|
||||
ActorMessageDispatcherInfo actorMessageDispatcherInfo = new(actorMessageHandlerAttribute.SceneType, imHandler);
|
||||
|
||||
this.RegisterHandler(messageType, actorMessageDispatcherInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterHandler(Type type, ActorMessageDispatcherInfo handler)
|
||||
{
|
||||
if (!this.ActorMessageHandlers.ContainsKey(type))
|
||||
{
|
||||
this.ActorMessageHandlers.Add(type, new List<ActorMessageDispatcherInfo>());
|
||||
}
|
||||
|
||||
this.ActorMessageHandlers[type].Add(handler);
|
||||
}
|
||||
|
||||
public async ETTask Handle(Entity entity, Address fromAddress, MessageObject message)
|
||||
{
|
||||
List<ActorMessageDispatcherInfo> list;
|
||||
if (!this.ActorMessageHandlers.TryGetValue(message.GetType(), out list))
|
||||
{
|
||||
throw new Exception($"not found message handler: {message} {entity.GetType().FullName}");
|
||||
}
|
||||
|
||||
SceneType sceneType = entity.IScene.SceneType;
|
||||
foreach (ActorMessageDispatcherInfo actorMessageDispatcherInfo in list)
|
||||
{
|
||||
if (!actorMessageDispatcherInfo.SceneType.HasSameFlag(sceneType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
await actorMessageDispatcherInfo.IMActorHandler.Handle(entity, fromAddress, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 294f1f138d2646e48b9bcbf0735b1e40
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public class ActorMessageHandlerAttribute: BaseAttribute
|
||||
{
|
||||
public SceneType SceneType { get; }
|
||||
|
||||
public ActorMessageHandlerAttribute(SceneType sceneType)
|
||||
{
|
||||
this.SceneType = sceneType;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4030c4594ee40704e893482747b7777e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public class ActorMessageLocationHandlerAttribute: ActorMessageHandlerAttribute
|
||||
{
|
||||
public ActorMessageLocationHandlerAttribute(SceneType sceneType): base(sceneType)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93635a0b30dc9c940af1c28d9614db92
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,67 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public struct ActorMessageInfo
|
||||
{
|
||||
public ActorId ActorId;
|
||||
public MessageObject MessageObject;
|
||||
}
|
||||
|
||||
public class ActorMessageQueue: Singleton<ActorMessageQueue>, ISingletonAwake
|
||||
{
|
||||
private readonly ConcurrentDictionary<int, ConcurrentQueue<ActorMessageInfo>> messages = new();
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
}
|
||||
|
||||
public void Send(ActorId actorId, MessageObject messageObject)
|
||||
{
|
||||
this.Send(actorId.Address, actorId, messageObject);
|
||||
}
|
||||
|
||||
public void Reply(ActorId actorId, MessageObject messageObject)
|
||||
{
|
||||
this.Send(actorId.Address, actorId, messageObject);
|
||||
}
|
||||
|
||||
public void Send(Address fromAddress, ActorId actorId, MessageObject messageObject)
|
||||
{
|
||||
if (!this.messages.TryGetValue(actorId.Address.Fiber, out var queue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
queue.Enqueue(new ActorMessageInfo() {ActorId = new ActorId(fromAddress, actorId.InstanceId), MessageObject = messageObject});
|
||||
}
|
||||
|
||||
public void Fetch(int fiberId, int count, List<ActorMessageInfo> list)
|
||||
{
|
||||
if (!this.messages.TryGetValue(fiberId, out var queue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
if (!queue.TryDequeue(out ActorMessageInfo message))
|
||||
{
|
||||
break;
|
||||
}
|
||||
list.Add(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddQueue(int fiberId)
|
||||
{
|
||||
var queue = new ConcurrentQueue<ActorMessageInfo>();
|
||||
this.messages[fiberId] = queue;
|
||||
}
|
||||
|
||||
public void RemoveQueue(int fiberId)
|
||||
{
|
||||
this.messages.TryRemove(fiberId, out _);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bc600297c654056823d9ac4821e69b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public interface IMActorHandler
|
||||
{
|
||||
ETTask Handle(Entity entity, Address fromAddress, MessageObject actorMessage);
|
||||
Type GetRequestType();
|
||||
Type GetResponseType();
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5dc447b842d71c4aa4ff84f98d4f5e4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/GameScripts/DotNet/Core/World/Module/Config.meta
Normal file
8
Assets/GameScripts/DotNet/Core/World/Module/Config.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fdea67585847303418ec523256daa007
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class ConfigAttribute: BaseAttribute
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5237b38531c8cea41a44538f14db11c1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
/// <summary>
|
||||
/// Config组件会扫描所有的有ConfigAttribute标签的配置,加载进来
|
||||
/// </summary>
|
||||
public class ConfigComponent: Singleton<ConfigComponent>, ISingletonAwake
|
||||
{
|
||||
public struct GetAllConfigBytes
|
||||
{
|
||||
}
|
||||
|
||||
public struct GetOneConfigBytes
|
||||
{
|
||||
public string ConfigName;
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<Type, ISingleton> allConfig = new();
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
}
|
||||
|
||||
public void Reload(Type configType)
|
||||
{
|
||||
byte[] oneConfigBytes =
|
||||
EventSystem.Instance.Invoke<GetOneConfigBytes, byte[]>(new GetOneConfigBytes() { ConfigName = configType.Name });
|
||||
|
||||
object category = MongoHelper.Deserialize(configType, oneConfigBytes, 0, oneConfigBytes.Length);
|
||||
ISingleton singleton = category as ISingleton;
|
||||
this.allConfig[configType] = singleton;
|
||||
|
||||
singleton.Register();
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
this.allConfig.Clear();
|
||||
Dictionary<Type, byte[]> configBytes = EventSystem.Instance.Invoke<GetAllConfigBytes, Dictionary<Type, byte[]>>(new GetAllConfigBytes());
|
||||
|
||||
foreach (Type type in configBytes.Keys)
|
||||
{
|
||||
byte[] oneConfigBytes = configBytes[type];
|
||||
this.LoadOneInThread(type, oneConfigBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public async ETTask LoadAsync()
|
||||
{
|
||||
this.allConfig.Clear();
|
||||
Dictionary<Type, byte[]> configBytes = EventSystem.Instance.Invoke<GetAllConfigBytes, Dictionary<Type, byte[]>>(new GetAllConfigBytes());
|
||||
|
||||
using ListComponent<Task> listTasks = ListComponent<Task>.Create();
|
||||
|
||||
foreach (Type type in configBytes.Keys)
|
||||
{
|
||||
byte[] oneConfigBytes = configBytes[type];
|
||||
Task task = Task.Run(() => LoadOneInThread(type, oneConfigBytes));
|
||||
listTasks.Add(task);
|
||||
}
|
||||
|
||||
await Task.WhenAll(listTasks.ToArray());
|
||||
}
|
||||
|
||||
private void LoadOneInThread(Type configType, byte[] oneConfigBytes)
|
||||
{
|
||||
object category = MongoHelper.Deserialize(configType, oneConfigBytes, 0, oneConfigBytes.Length);
|
||||
|
||||
lock (this)
|
||||
{
|
||||
ISingleton singleton = category as ISingleton;
|
||||
this.allConfig[configType] = singleton;
|
||||
|
||||
singleton.Register();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3220f986cb182d4da3952ba65888ae4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public abstract class ConfigSingleton<T>: ISingleton, ISupportInitialize where T: ConfigSingleton<T>, new()
|
||||
{
|
||||
private bool isDisposed;
|
||||
|
||||
[StaticField]
|
||||
private static T instance;
|
||||
|
||||
[StaticField]
|
||||
private static object lockObj = new();
|
||||
|
||||
public static T Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
private set
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
instance = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Register()
|
||||
{
|
||||
Instance = (T)this;
|
||||
}
|
||||
|
||||
public bool IsDisposed()
|
||||
{
|
||||
return this.isDisposed;
|
||||
}
|
||||
|
||||
protected virtual void Destroy()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (this.isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDisposed = true;
|
||||
|
||||
Instance = null;
|
||||
|
||||
this.Destroy();
|
||||
}
|
||||
|
||||
public virtual void BeginInit()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void EndInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3f57aff7b8dfc3489d97b45c8423bc8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,10 @@
|
||||
namespace ET
|
||||
{
|
||||
/// <summary>
|
||||
/// 每个Config的基类
|
||||
/// </summary>
|
||||
public interface IConfig
|
||||
{
|
||||
int Id { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 986a79748635bf741ba4ba0ac5e68d5b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,7 @@
|
||||
namespace ET
|
||||
{
|
||||
public interface IMerge
|
||||
{
|
||||
void Merge(object o);
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2d3bd3ce794a884e96904d2f9f5b5b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f63520dc272ce84894929d06d86966b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class BaseAttribute: Attribute
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d33b65898d50804188d439209df30d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public class EventAttribute: BaseAttribute
|
||||
{
|
||||
public SceneType SceneType { get; }
|
||||
|
||||
public EventAttribute(SceneType sceneType)
|
||||
{
|
||||
this.SceneType = sceneType;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68a635393e1b00c489870d0fb6a97f8b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,250 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public class EventSystem: Singleton<EventSystem>, ISingletonAwake<Dictionary<string, Type>>
|
||||
{
|
||||
private class EventInfo
|
||||
{
|
||||
public IEvent IEvent { get; }
|
||||
|
||||
public SceneType SceneType {get; }
|
||||
|
||||
public EventInfo(IEvent iEvent, SceneType sceneType)
|
||||
{
|
||||
this.IEvent = iEvent;
|
||||
this.SceneType = sceneType;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, Type> allTypes = new();
|
||||
|
||||
private readonly UnOrderMultiMapSet<Type, Type> types = new();
|
||||
|
||||
private readonly Dictionary<Type, List<EventInfo>> allEvents = new();
|
||||
|
||||
private Dictionary<Type, Dictionary<long, object>> allInvokes = new();
|
||||
|
||||
public void Awake(Dictionary<string, Type> addTypes)
|
||||
{
|
||||
this.allTypes.Clear();
|
||||
this.types.Clear();
|
||||
|
||||
foreach ((string fullName, Type type) in addTypes)
|
||||
{
|
||||
this.allTypes[fullName] = type;
|
||||
|
||||
if (type.IsAbstract)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 记录所有的有BaseAttribute标记的的类型
|
||||
object[] objects = type.GetCustomAttributes(typeof(BaseAttribute), true);
|
||||
|
||||
foreach (object o in objects)
|
||||
{
|
||||
this.types.Add(o.GetType(), type);
|
||||
}
|
||||
}
|
||||
|
||||
this.allEvents.Clear();
|
||||
foreach (Type type in types[typeof (EventAttribute)])
|
||||
{
|
||||
IEvent obj = Activator.CreateInstance(type) as IEvent;
|
||||
if (obj == null)
|
||||
{
|
||||
throw new Exception($"type not is AEvent: {type.Name}");
|
||||
}
|
||||
|
||||
object[] attrs = type.GetCustomAttributes(typeof(EventAttribute), false);
|
||||
foreach (object attr in attrs)
|
||||
{
|
||||
EventAttribute eventAttribute = attr as EventAttribute;
|
||||
|
||||
Type eventType = obj.Type;
|
||||
|
||||
EventInfo eventInfo = new(obj, eventAttribute.SceneType);
|
||||
|
||||
if (!this.allEvents.ContainsKey(eventType))
|
||||
{
|
||||
this.allEvents.Add(eventType, new List<EventInfo>());
|
||||
}
|
||||
this.allEvents[eventType].Add(eventInfo);
|
||||
}
|
||||
}
|
||||
|
||||
this.allInvokes = new Dictionary<Type, Dictionary<long, object>>();
|
||||
foreach (Type type in types[typeof (InvokeAttribute)])
|
||||
{
|
||||
object obj = Activator.CreateInstance(type);
|
||||
IInvoke iInvoke = obj as IInvoke;
|
||||
if (iInvoke == null)
|
||||
{
|
||||
throw new Exception($"type not is callback: {type.Name}");
|
||||
}
|
||||
|
||||
object[] attrs = type.GetCustomAttributes(typeof(InvokeAttribute), false);
|
||||
foreach (object attr in attrs)
|
||||
{
|
||||
if (!this.allInvokes.TryGetValue(iInvoke.Type, out var dict))
|
||||
{
|
||||
dict = new Dictionary<long, object>();
|
||||
this.allInvokes.Add(iInvoke.Type, dict);
|
||||
}
|
||||
|
||||
InvokeAttribute invokeAttribute = attr as InvokeAttribute;
|
||||
|
||||
try
|
||||
{
|
||||
dict.Add(invokeAttribute.Type, obj);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception($"action type duplicate: {iInvoke.Type.Name} {invokeAttribute.Type}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HashSet<Type> GetTypes(Type systemAttributeType)
|
||||
{
|
||||
if (!this.types.ContainsKey(systemAttributeType))
|
||||
{
|
||||
return new HashSet<Type>();
|
||||
}
|
||||
|
||||
return this.types[systemAttributeType];
|
||||
}
|
||||
|
||||
public Dictionary<string, Type> GetTypes()
|
||||
{
|
||||
return allTypes;
|
||||
}
|
||||
|
||||
public Type GetType(string typeName)
|
||||
{
|
||||
return this.allTypes[typeName];
|
||||
}
|
||||
|
||||
public async ETTask PublishAsync<S, T>(S scene, T a) where S: class, IScene where T : struct
|
||||
{
|
||||
List<EventInfo> iEvents;
|
||||
if (!this.allEvents.TryGetValue(typeof(T), out iEvents))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using ListComponent<ETTask> list = ListComponent<ETTask>.Create();
|
||||
|
||||
foreach (EventInfo eventInfo in iEvents)
|
||||
{
|
||||
if (!scene.SceneType.HasSameFlag(eventInfo.SceneType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(eventInfo.IEvent is AEvent<S, T> aEvent))
|
||||
{
|
||||
Log.Error($"event error: {eventInfo.IEvent.GetType().FullName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
list.Add(aEvent.Handle(scene, a));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await ETTaskHelper.WaitAll(list);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void Publish<S, T>(S scene, T a) where S: class, IScene where T : struct
|
||||
{
|
||||
List<EventInfo> iEvents;
|
||||
if (!this.allEvents.TryGetValue(typeof (T), out iEvents))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SceneType sceneType = scene.SceneType;
|
||||
foreach (EventInfo eventInfo in iEvents)
|
||||
{
|
||||
if (!sceneType.HasSameFlag(eventInfo.SceneType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (!(eventInfo.IEvent is AEvent<S, T> aEvent))
|
||||
{
|
||||
Log.Error($"event error: {eventInfo.IEvent.GetType().FullName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
aEvent.Handle(scene, a).Coroutine();
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke跟Publish的区别(特别注意)
|
||||
// Invoke类似函数,必须有被调用方,否则异常,调用者跟被调用者属于同一模块,比如MoveComponent中的Timer计时器,调用跟被调用的代码均属于移动模块
|
||||
// 既然Invoke跟函数一样,那么为什么不使用函数呢? 因为有时候不方便直接调用,比如Config加载,在客户端跟服务端加载方式不一样。比如TimerComponent需要根据Id分发
|
||||
// 注意,不要把Invoke当函数使用,这样会造成代码可读性降低,能用函数不要用Invoke
|
||||
// publish是事件,抛出去可以没人订阅,调用者跟被调用者属于两个模块,比如任务系统需要知道道具使用的信息,则订阅道具使用事件
|
||||
public void Invoke<A>(long type, A args) where A: struct
|
||||
{
|
||||
if (!this.allInvokes.TryGetValue(typeof(A), out var invokeHandlers))
|
||||
{
|
||||
throw new Exception($"Invoke error1: {type} {typeof(A).FullName}");
|
||||
}
|
||||
if (!invokeHandlers.TryGetValue(type, out var invokeHandler))
|
||||
{
|
||||
throw new Exception($"Invoke error2: {type} {typeof(A).FullName}");
|
||||
}
|
||||
|
||||
var aInvokeHandler = invokeHandler as AInvokeHandler<A>;
|
||||
if (aInvokeHandler == null)
|
||||
{
|
||||
throw new Exception($"Invoke error3, not AInvokeHandler: {type} {typeof(A).FullName}");
|
||||
}
|
||||
|
||||
aInvokeHandler.Handle(args);
|
||||
}
|
||||
|
||||
public T Invoke<A, T>(long type, A args) where A: struct
|
||||
{
|
||||
if (!this.allInvokes.TryGetValue(typeof(A), out var invokeHandlers))
|
||||
{
|
||||
throw new Exception($"Invoke error4: {type} {typeof(A).FullName}");
|
||||
}
|
||||
|
||||
if (!invokeHandlers.TryGetValue(type, out var invokeHandler))
|
||||
{
|
||||
throw new Exception($"Invoke error5: {type} {typeof(A).FullName}");
|
||||
}
|
||||
|
||||
var aInvokeHandler = invokeHandler as AInvokeHandler<A, T>;
|
||||
if (aInvokeHandler == null)
|
||||
{
|
||||
throw new Exception($"Invoke error6, not AInvokeHandler: {type} {typeof(A).FullName} {typeof(T).FullName} ");
|
||||
}
|
||||
|
||||
return aInvokeHandler.Handle(args);
|
||||
}
|
||||
|
||||
public void Invoke<A>(A args) where A: struct
|
||||
{
|
||||
Invoke(0, args);
|
||||
}
|
||||
|
||||
public T Invoke<A, T>(A args) where A: struct
|
||||
{
|
||||
return Invoke<A, T>(0, args);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d55d77ba949f0545a29e965a7a5b2bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public interface IEvent
|
||||
{
|
||||
Type Type { get; }
|
||||
}
|
||||
|
||||
public abstract class AEvent<S, A>: IEvent where S: class, IScene where A: struct
|
||||
{
|
||||
public Type Type
|
||||
{
|
||||
get
|
||||
{
|
||||
return typeof (A);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract ETTask Run(S scene, A a);
|
||||
|
||||
public async ETTask Handle(S scene, A a)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Run(scene, a);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37f03a5bbacabb74496f35229db66c95
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public interface IInvoke
|
||||
{
|
||||
Type Type { get; }
|
||||
}
|
||||
|
||||
public abstract class AInvokeHandler<A>: IInvoke where A: struct
|
||||
{
|
||||
public Type Type
|
||||
{
|
||||
get
|
||||
{
|
||||
return typeof (A);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void Handle(A args);
|
||||
}
|
||||
|
||||
public abstract class AInvokeHandler<A, T>: IInvoke where A: struct
|
||||
{
|
||||
public Type Type
|
||||
{
|
||||
get
|
||||
{
|
||||
return typeof (A);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract T Handle(A args);
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fec8a605631db024fa246feb87beb5d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,12 @@
|
||||
namespace ET
|
||||
{
|
||||
public class InvokeAttribute: BaseAttribute
|
||||
{
|
||||
public long Type { get; }
|
||||
|
||||
public InvokeAttribute(long type = 0)
|
||||
{
|
||||
this.Type = type;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31ef4af33bd048f49b0b9b0d9a9a0547
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public class TypeSystems
|
||||
{
|
||||
public class OneTypeSystems
|
||||
{
|
||||
public OneTypeSystems(int count)
|
||||
{
|
||||
this.QueueFlag = new bool[count];
|
||||
}
|
||||
|
||||
public readonly UnOrderMultiMap<Type, object> Map = new();
|
||||
// 这里不用hash,数量比较少,直接for循环速度更快
|
||||
public readonly bool[] QueueFlag;
|
||||
}
|
||||
|
||||
private readonly int count;
|
||||
|
||||
public TypeSystems(int count)
|
||||
{
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
private readonly Dictionary<Type, OneTypeSystems> typeSystemsMap = new();
|
||||
|
||||
public OneTypeSystems GetOrCreateOneTypeSystems(Type type)
|
||||
{
|
||||
OneTypeSystems systems = null;
|
||||
this.typeSystemsMap.TryGetValue(type, out systems);
|
||||
if (systems != null)
|
||||
{
|
||||
return systems;
|
||||
}
|
||||
|
||||
systems = new OneTypeSystems(this.count);
|
||||
this.typeSystemsMap.Add(type, systems);
|
||||
return systems;
|
||||
}
|
||||
|
||||
public OneTypeSystems GetOneTypeSystems(Type type)
|
||||
{
|
||||
OneTypeSystems systems = null;
|
||||
this.typeSystemsMap.TryGetValue(type, out systems);
|
||||
return systems;
|
||||
}
|
||||
|
||||
public List<object> GetSystems(Type type, Type systemType)
|
||||
{
|
||||
OneTypeSystems oneTypeSystems = null;
|
||||
if (!this.typeSystemsMap.TryGetValue(type, out oneTypeSystems))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!oneTypeSystems.Map.TryGetValue(systemType, out List<object> systems))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return systems;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0eda11a6dcbc4874882857077fa35a2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/GameScripts/DotNet/Core/World/Module/Fiber.meta
Normal file
8
Assets/GameScripts/DotNet/Core/World/Module/Fiber.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c7dd8d20e9ef5643a4f3a61fd9cc010
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
namespace ET
|
||||
{
|
||||
|
||||
public struct FiberInit
|
||||
{
|
||||
public Fiber Fiber { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58e3cff11bc93e145b00d4243dcb1538
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public enum SchedulerType
|
||||
{
|
||||
Main,
|
||||
Thread,
|
||||
ThreadPool,
|
||||
}
|
||||
|
||||
public class FiberManager: Singleton<FiberManager>, ISingletonAwake
|
||||
{
|
||||
private readonly IScheduler[] schedulers = new IScheduler[3];
|
||||
|
||||
private int idGenerator = 10000000; // 10000000以下为保留的用于StartSceneConfig的fiber id, 1个区配置1000个纤程,可以配置10000个区
|
||||
private ConcurrentDictionary<int, Fiber> fibers = new();
|
||||
|
||||
private MainThreadScheduler mainThreadScheduler;
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
this.mainThreadScheduler = new MainThreadScheduler(this);
|
||||
this.schedulers[(int)SchedulerType.Main] = this.mainThreadScheduler;
|
||||
|
||||
#if ENABLE_VIEW && UNITY_EDITOR
|
||||
this.schedulers[(int)SchedulerType.Thread] = this.mainThreadScheduler;
|
||||
this.schedulers[(int)SchedulerType.ThreadPool] = this.mainThreadScheduler;
|
||||
#else
|
||||
this.schedulers[(int)SchedulerType.Thread] = new ThreadScheduler(this);
|
||||
this.schedulers[(int)SchedulerType.ThreadPool] = new ThreadPoolScheduler(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
this.mainThreadScheduler.Update();
|
||||
}
|
||||
|
||||
public void LateUpdate()
|
||||
{
|
||||
this.mainThreadScheduler.LateUpdate();
|
||||
}
|
||||
|
||||
protected override void Destroy()
|
||||
{
|
||||
foreach (IScheduler scheduler in this.schedulers)
|
||||
{
|
||||
scheduler.Dispose();
|
||||
}
|
||||
|
||||
foreach (var kv in this.fibers)
|
||||
{
|
||||
kv.Value.Dispose();
|
||||
}
|
||||
|
||||
this.fibers = null;
|
||||
}
|
||||
|
||||
public async Task<int> Create(SchedulerType schedulerType, int fiberId, int zone, SceneType sceneType, string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
Fiber fiber = new(fiberId, Options.Instance.Process, zone, sceneType, name);
|
||||
|
||||
this.fibers.TryAdd(fiberId, fiber);
|
||||
this.schedulers[(int) schedulerType].Add(fiberId);
|
||||
|
||||
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
fiber.ThreadSynchronizationContext.Post(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 根据Fiber的SceneType分发Init,必须在Fiber线程中执行
|
||||
await EventSystem.Instance.Invoke<FiberInit, ETTask>((long)sceneType, new FiberInit() {Fiber = fiber});
|
||||
tcs.SetResult(true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"init fiber fail: {sceneType} {e}");
|
||||
}
|
||||
});
|
||||
|
||||
await tcs.Task;
|
||||
return fiberId;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception($"create fiber error: {fiberId} {sceneType}", e);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> Create(SchedulerType schedulerType, int zone, SceneType sceneType, string name)
|
||||
{
|
||||
int fiberId = Interlocked.Increment(ref this.idGenerator);
|
||||
return await this.Create(schedulerType, fiberId, zone, sceneType, name);
|
||||
}
|
||||
|
||||
public void Remove(int id)
|
||||
{
|
||||
Fiber fiber = this.Get(id);
|
||||
fiber.ThreadSynchronizationContext.Post(()=>{fiber.Dispose();});
|
||||
// 这里不能dispose,因为有可能fiber还在运行,会出现线程竞争
|
||||
//fiber.Dispose();
|
||||
}
|
||||
|
||||
internal void RemoveReal(int id)
|
||||
{
|
||||
this.fibers.Remove(id, out _);
|
||||
// 这里不能dispose,因为有可能fiber还在运行,会出现线程竞争
|
||||
//fiber.Dispose();
|
||||
}
|
||||
|
||||
// 不允许外部调用,容易出现多线程问题, 只能通过消息通信,不允许直接获取其它Fiber引用
|
||||
internal Fiber Get(int id)
|
||||
{
|
||||
this.fibers.TryGetValue(id, out Fiber fiber);
|
||||
return fiber;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f3c27be2da070a4384fb15a77ba2c33
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public interface IScheduler: IDisposable
|
||||
{
|
||||
void Add(int fiberId);
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8de74cf443d4f743a98f86531df4360
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,97 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
internal class MainThreadScheduler: IScheduler
|
||||
{
|
||||
private readonly ConcurrentQueue<int> idQueue = new();
|
||||
private readonly ConcurrentQueue<int> addIds = new();
|
||||
private readonly FiberManager fiberManager;
|
||||
private readonly ThreadSynchronizationContext threadSynchronizationContext = new();
|
||||
|
||||
public MainThreadScheduler(FiberManager fiberManager)
|
||||
{
|
||||
SynchronizationContext.SetSynchronizationContext(this.threadSynchronizationContext);
|
||||
this.fiberManager = fiberManager;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.addIds.Clear();
|
||||
this.idQueue.Clear();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
SynchronizationContext.SetSynchronizationContext(this.threadSynchronizationContext);
|
||||
this.threadSynchronizationContext.Update();
|
||||
|
||||
int count = this.idQueue.Count;
|
||||
while (count-- > 0)
|
||||
{
|
||||
if (!this.idQueue.TryDequeue(out int id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Fiber fiber = this.fiberManager.Get(id);
|
||||
if (fiber == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fiber.IsDisposed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
SynchronizationContext.SetSynchronizationContext(fiber.ThreadSynchronizationContext);
|
||||
fiber.Update();
|
||||
|
||||
this.idQueue.Enqueue(id);
|
||||
}
|
||||
}
|
||||
|
||||
public void LateUpdate()
|
||||
{
|
||||
int count = this.idQueue.Count;
|
||||
while (count-- > 0)
|
||||
{
|
||||
if (!this.idQueue.TryDequeue(out int id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Fiber fiber = this.fiberManager.Get(id);
|
||||
if (fiber == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fiber.IsDisposed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
SynchronizationContext.SetSynchronizationContext(fiber.ThreadSynchronizationContext);
|
||||
fiber.LateUpdate();
|
||||
|
||||
this.idQueue.Enqueue(id);
|
||||
}
|
||||
|
||||
while (this.addIds.Count > 0)
|
||||
{
|
||||
this.addIds.TryDequeue(out int result);
|
||||
this.idQueue.Enqueue(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Add(int fiberId = 0)
|
||||
{
|
||||
this.addIds.Enqueue(fiberId);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd9f51e035ab0784586df7b7e604072e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
internal class ThreadPoolScheduler: IScheduler
|
||||
{
|
||||
private readonly List<Thread> threads;
|
||||
|
||||
private readonly ConcurrentQueue<int> idQueue = new();
|
||||
|
||||
private readonly FiberManager fiberManager;
|
||||
|
||||
public ThreadPoolScheduler(FiberManager fiberManager)
|
||||
{
|
||||
this.fiberManager = fiberManager;
|
||||
int threadCount = Environment.ProcessorCount;
|
||||
this.threads = new List<Thread>(threadCount);
|
||||
for (int i = 0; i < threadCount; ++i)
|
||||
{
|
||||
Thread thread = new(this.Loop);
|
||||
this.threads.Add(thread);
|
||||
thread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void Loop()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (this.fiberManager.IsDisposed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.idQueue.TryDequeue(out int id))
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
Fiber fiber = this.fiberManager.Get(id);
|
||||
if (fiber == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fiber.IsDisposed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SynchronizationContext.SetSynchronizationContext(fiber.ThreadSynchronizationContext);
|
||||
fiber.Update();
|
||||
fiber.LateUpdate();
|
||||
SynchronizationContext.SetSynchronizationContext(null);
|
||||
|
||||
this.idQueue.Enqueue(id);
|
||||
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (Thread thread in this.threads)
|
||||
{
|
||||
thread.Join();
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(int fiberId)
|
||||
{
|
||||
this.idQueue.Enqueue(fiberId);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d14b7a106ddb98e4f928f6aeef60d86f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
// 一个Fiber一个固定的线程
|
||||
internal class ThreadScheduler: IScheduler
|
||||
{
|
||||
private readonly ConcurrentDictionary<int, Thread> dictionary = new();
|
||||
|
||||
private readonly FiberManager fiberManager;
|
||||
|
||||
public ThreadScheduler(FiberManager fiberManager)
|
||||
{
|
||||
this.fiberManager = fiberManager;
|
||||
}
|
||||
|
||||
private void Loop(int fiberId)
|
||||
{
|
||||
Fiber fiber = fiberManager.Get(fiberId);
|
||||
SynchronizationContext.SetSynchronizationContext(fiber.ThreadSynchronizationContext);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (this.fiberManager.IsDisposed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fiber = fiberManager.Get(fiberId);
|
||||
if (fiber == null)
|
||||
{
|
||||
this.dictionary.Remove(fiberId, out _);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fiber.IsDisposed)
|
||||
{
|
||||
this.dictionary.Remove(fiberId, out _);
|
||||
return;
|
||||
}
|
||||
|
||||
fiber.Update();
|
||||
fiber.LateUpdate();
|
||||
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var kv in this.dictionary.ToArray())
|
||||
{
|
||||
kv.Value.Join();
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(int fiberId)
|
||||
{
|
||||
Thread thread = new(() => this.Loop(fiberId));
|
||||
this.dictionary.TryAdd(fiberId, thread);
|
||||
thread.Start();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7267a4e71eb917547afb0bdd52d9a360
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e3e2aed9e0845edb35993d7496f871f
|
||||
timeCreated: 1687098052
|
@@ -0,0 +1,27 @@
|
||||
namespace ET
|
||||
{
|
||||
public class IdValueGenerater: Singleton<IdValueGenerater>, ISingletonAwake
|
||||
{
|
||||
private uint value;
|
||||
|
||||
public uint Value
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (++this.value > IdGenerater.Mask20bit - 1)
|
||||
{
|
||||
this.value = 0;
|
||||
}
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4510ce4f5e843f9a9efc99d1714845a
|
||||
timeCreated: 1687098066
|
8
Assets/GameScripts/DotNet/Core/World/Module/Log.meta
Normal file
8
Assets/GameScripts/DotNet/Core/World/Module/Log.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 057d576e460d8e44182a6f4b62585eb0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
17
Assets/GameScripts/DotNet/Core/World/Module/Log/ILog.cs
Normal file
17
Assets/GameScripts/DotNet/Core/World/Module/Log/ILog.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace ET
|
||||
{
|
||||
public interface ILog
|
||||
{
|
||||
void Trace(string message);
|
||||
void Warning(string message);
|
||||
void Info(string message);
|
||||
void Debug(string message);
|
||||
void Error(string message);
|
||||
void Fatal(string message);
|
||||
void Trace(string message, params object[] args);
|
||||
void Warning(string message, params object[] args);
|
||||
void Info(string message, params object[] args);
|
||||
void Debug(string message, params object[] args);
|
||||
void Error(string message, params object[] args);
|
||||
}
|
||||
}
|
11
Assets/GameScripts/DotNet/Core/World/Module/Log/ILog.cs.meta
Normal file
11
Assets/GameScripts/DotNet/Core/World/Module/Log/ILog.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63e88312339ec39479bf5054c7f5698b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
90
Assets/GameScripts/DotNet/Core/World/Module/Log/Log.cs
Normal file
90
Assets/GameScripts/DotNet/Core/World/Module/Log/Log.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public static class Log
|
||||
{
|
||||
[Conditional("DEBUG")]
|
||||
public static void Trace(string msg)
|
||||
{
|
||||
Logger.Instance.Trace(msg);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public static void Debug(string msg)
|
||||
{
|
||||
Logger.Instance.Debug(msg);
|
||||
}
|
||||
|
||||
public static void Info(string msg)
|
||||
{
|
||||
Logger.Instance.Info(msg);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public static void Warning(string msg)
|
||||
{
|
||||
Logger.Instance.Warning(msg);
|
||||
}
|
||||
|
||||
public static void Error(string msg)
|
||||
{
|
||||
Logger.Instance.Error(msg);
|
||||
}
|
||||
|
||||
public static void Fatal(Exception msg)
|
||||
{
|
||||
Logger.Instance.Fatal(msg.Message);
|
||||
}
|
||||
|
||||
public static void Fatal(string msg)
|
||||
{
|
||||
Logger.Instance.Fatal(msg);
|
||||
}
|
||||
|
||||
public static void Error(Exception msg)
|
||||
{
|
||||
Logger.Instance.Error(msg);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public static void Console(string msg)
|
||||
{
|
||||
Logger.Instance.Console(msg);
|
||||
}
|
||||
|
||||
#if DOTNET
|
||||
[Conditional("DEBUG")]
|
||||
public static void Trace(ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler message)
|
||||
{
|
||||
Logger.Instance.Trace(message.ToStringAndClear());
|
||||
}
|
||||
[Conditional("DEBUG")]
|
||||
public static void Warning(ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler message)
|
||||
{
|
||||
Logger.Instance.Warning(message.ToStringAndClear());
|
||||
}
|
||||
|
||||
public static void Info(ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler message)
|
||||
{
|
||||
Logger.Instance.Info(message.ToStringAndClear());
|
||||
}
|
||||
[Conditional("DEBUG")]
|
||||
public static void Debug(ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler message)
|
||||
{
|
||||
Logger.Instance.Debug(message.ToStringAndClear());
|
||||
}
|
||||
|
||||
public static void Error(ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler message)
|
||||
{
|
||||
Logger.Instance.Error(message.ToStringAndClear());
|
||||
}
|
||||
[Conditional("DEBUG")]
|
||||
public static void Console(ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler message)
|
||||
{
|
||||
Logger.Instance.Console(message.ToStringAndClear());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
11
Assets/GameScripts/DotNet/Core/World/Module/Log/Log.cs.meta
Normal file
11
Assets/GameScripts/DotNet/Core/World/Module/Log/Log.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bdc1761525ecfa642b1780a6b4e0243d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
294
Assets/GameScripts/DotNet/Core/World/Module/Log/Logger.cs
Normal file
294
Assets/GameScripts/DotNet/Core/World/Module/Log/Logger.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public class Logger : Singleton<Logger>, ISingletonAwake
|
||||
{
|
||||
private ILog iLog;
|
||||
|
||||
public ILog ILog
|
||||
{
|
||||
set { this.iLog = value; }
|
||||
}
|
||||
|
||||
public enum LogLevel
|
||||
{
|
||||
INFO,
|
||||
SUCCESSED,
|
||||
ASSERT,
|
||||
WARNING,
|
||||
ERROR,
|
||||
EXCEPTION,
|
||||
}
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
}
|
||||
|
||||
private LogLevel _filterLevel = LogLevel.INFO;
|
||||
private StringBuilder _stringBuilder = new StringBuilder();
|
||||
|
||||
private const int TraceLevel = 1;
|
||||
private const int DebugLevel = 2;
|
||||
private const int InfoLevel = 3;
|
||||
private const int WarningLevel = 4;
|
||||
|
||||
private bool CheckLogLevel(int level)
|
||||
{
|
||||
if (Options.Instance == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Options.Instance.LogLevel <= level;
|
||||
}
|
||||
|
||||
public void Trace(string msg)
|
||||
{
|
||||
if (!CheckLogLevel(DebugLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StackTrace st = new StackTrace(2, true);
|
||||
string log = $"{msg}\n{st}";
|
||||
this.Log(LogLevel.INFO, log);
|
||||
this.iLog.Trace(log);
|
||||
}
|
||||
|
||||
public void Debug(string msg)
|
||||
{
|
||||
if (!CheckLogLevel(DebugLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
this.Log(LogLevel.INFO, msg);
|
||||
this.iLog.Debug(msg);
|
||||
}
|
||||
|
||||
public void Info(string msg)
|
||||
{
|
||||
if (!CheckLogLevel(InfoLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
this.Log(LogLevel.INFO, msg);
|
||||
this.iLog.Info(msg);
|
||||
}
|
||||
|
||||
public void TraceInfo(string msg)
|
||||
{
|
||||
if (!CheckLogLevel(InfoLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StackTrace st = new StackTrace(2, true);
|
||||
string log = $"{msg}\n{st}";
|
||||
this.Log(LogLevel.INFO, msg);
|
||||
this.iLog.Trace(log);
|
||||
}
|
||||
|
||||
public void Warning(string msg)
|
||||
{
|
||||
if (!CheckLogLevel(WarningLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
this.Log(LogLevel.WARNING, msg);
|
||||
this.iLog.Warning(msg);
|
||||
}
|
||||
|
||||
public void Error(string msg)
|
||||
{
|
||||
StackTrace st = new StackTrace(2, true);
|
||||
string log = $"{msg}\n{st}";
|
||||
this.Log(LogLevel.ERROR, log);
|
||||
this.iLog.Error(log);
|
||||
}
|
||||
|
||||
public void Error(Exception e)
|
||||
{
|
||||
if (e.Data.Contains("StackTrace"))
|
||||
{
|
||||
this.iLog.Error($"{e.Data["StackTrace"]}\n{e}");
|
||||
return;
|
||||
}
|
||||
|
||||
string str = e.ToString();
|
||||
this.Log(LogLevel.ERROR, str);
|
||||
this.iLog.Error(str);
|
||||
}
|
||||
|
||||
public void Fatal(string msg)
|
||||
{
|
||||
StackTrace st = new StackTrace(2, true);
|
||||
this.iLog.Fatal($"{msg}\n{st}");
|
||||
}
|
||||
|
||||
public void Trace(string message, params object[] args)
|
||||
{
|
||||
if (!CheckLogLevel(TraceLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StackTrace st = new StackTrace(2, true);
|
||||
this.iLog.Trace($"{string.Format(message, args)}\n{st}");
|
||||
}
|
||||
|
||||
public void Warning(string message, params object[] args)
|
||||
{
|
||||
if (!CheckLogLevel(WarningLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.iLog.Warning(string.Format(message, args));
|
||||
}
|
||||
|
||||
public void Info(string message, params object[] args)
|
||||
{
|
||||
if (!CheckLogLevel(InfoLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.iLog.Info(string.Format(message, args));
|
||||
}
|
||||
|
||||
public void Debug(string message, params object[] args)
|
||||
{
|
||||
if (!CheckLogLevel(DebugLevel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.iLog.Debug(string.Format(message, args));
|
||||
}
|
||||
|
||||
public void Error(string message, params object[] args)
|
||||
{
|
||||
StackTrace st = new StackTrace(2, true);
|
||||
string s = string.Format(message, args) + '\n' + st;
|
||||
this.iLog.Error(s);
|
||||
}
|
||||
|
||||
public void Console(string message)
|
||||
{
|
||||
if (Options.Instance.Console == 1)
|
||||
{
|
||||
this.Log(LogLevel.SUCCESSED, message);
|
||||
}
|
||||
|
||||
this.iLog.Debug(message);
|
||||
}
|
||||
|
||||
public void Console(string message, params object[] args)
|
||||
{
|
||||
string s = string.Format(message, args);
|
||||
if (Options.Instance.Console == 1)
|
||||
{
|
||||
System.Console.WriteLine(s);
|
||||
}
|
||||
|
||||
this.iLog.Debug(s);
|
||||
}
|
||||
|
||||
private StringBuilder GetFormatString(LogLevel logLevel, string logString)
|
||||
{
|
||||
_stringBuilder.Clear();
|
||||
switch (logLevel)
|
||||
{
|
||||
case LogLevel.SUCCESSED:
|
||||
_stringBuilder.AppendFormat(
|
||||
"[TEngineServer][SUCCESSED][{0}] - {1}",
|
||||
System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff"), logString);
|
||||
break;
|
||||
case LogLevel.INFO:
|
||||
_stringBuilder.AppendFormat(
|
||||
"[TEngineServer][INFO][{0}] - {1}",
|
||||
System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff"), logString);
|
||||
break;
|
||||
case LogLevel.ASSERT:
|
||||
_stringBuilder.AppendFormat(
|
||||
"[TEngineServer][ASSERT][{0}] - {1}",
|
||||
System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff"), logString);
|
||||
break;
|
||||
case LogLevel.WARNING:
|
||||
_stringBuilder.AppendFormat(
|
||||
"[TEngineServer][WARNING][{0}] - {1}", System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff"),
|
||||
logString);
|
||||
break;
|
||||
case LogLevel.ERROR:
|
||||
_stringBuilder.AppendFormat(
|
||||
"[TEngineServer][ERROR][{0}] - {1}",
|
||||
System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff"), logString);
|
||||
break;
|
||||
case LogLevel.EXCEPTION:
|
||||
_stringBuilder.AppendFormat(
|
||||
"[TEngineServer][EXCEPTION][{0}] - {1}",
|
||||
System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff"), logString);
|
||||
break;
|
||||
}
|
||||
|
||||
return _stringBuilder;
|
||||
}
|
||||
|
||||
private void Log(LogLevel type, string logString)
|
||||
{
|
||||
if (type < _filterLevel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder infoBuilder = GetFormatString(type, logString);
|
||||
|
||||
if (type == LogLevel.ERROR || type == LogLevel.WARNING || type == LogLevel.EXCEPTION)
|
||||
{
|
||||
StackFrame[] sf = new StackTrace().GetFrames();
|
||||
for (int i = 0; i < sf.Length; i++)
|
||||
{
|
||||
StackFrame frame = sf[i];
|
||||
string declaringTypeName = frame.GetMethod()?.DeclaringType.FullName;
|
||||
string methodName = frame.GetMethod()?.Name;
|
||||
infoBuilder.AppendFormat("[{0}::{1}\n", declaringTypeName, methodName);
|
||||
}
|
||||
}
|
||||
|
||||
string logStr = infoBuilder.ToString();
|
||||
|
||||
if (type == LogLevel.INFO)
|
||||
{
|
||||
System.Console.ForegroundColor = ConsoleColor.White;
|
||||
System.Console.WriteLine(logStr);
|
||||
}
|
||||
else if (type == LogLevel.SUCCESSED)
|
||||
{
|
||||
System.Console.ForegroundColor = ConsoleColor.Green;
|
||||
System.Console.WriteLine(logStr);
|
||||
}
|
||||
else if (type == LogLevel.WARNING)
|
||||
{
|
||||
System.Console.ForegroundColor = ConsoleColor.DarkYellow;
|
||||
System.Console.WriteLine(logStr);
|
||||
}
|
||||
else if (type == LogLevel.ASSERT)
|
||||
{
|
||||
System.Console.ForegroundColor = ConsoleColor.Red;
|
||||
System.Console.WriteLine(logStr);
|
||||
}
|
||||
else if (type == LogLevel.ERROR)
|
||||
{
|
||||
System.Console.ForegroundColor = ConsoleColor.Red;
|
||||
System.Console.WriteLine(logStr);
|
||||
}
|
||||
else if (type == LogLevel.EXCEPTION)
|
||||
{
|
||||
System.Console.ForegroundColor = ConsoleColor.Magenta;
|
||||
System.Console.WriteLine(logStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d233baf6c97d1c4da67228e2a2e14e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
78
Assets/GameScripts/DotNet/Core/World/Module/Log/NLogger.cs
Normal file
78
Assets/GameScripts/DotNet/Core/World/Module/Log/NLogger.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using NLog;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public class NLogger: ILog
|
||||
{
|
||||
private readonly NLog.Logger logger;
|
||||
|
||||
public NLogger(string name, int process, string configPath)
|
||||
{
|
||||
LogManager.Configuration = new NLog.Config.XmlLoggingConfiguration(configPath);
|
||||
LogManager.Configuration.Variables["appIdFormat"] = $"{process:000000}";
|
||||
LogManager.Configuration.Variables["currentDir"] = Environment.CurrentDirectory;
|
||||
this.logger = LogManager.GetLogger(name);
|
||||
}
|
||||
|
||||
public void Trace(string message)
|
||||
{
|
||||
this.logger.Trace(message);
|
||||
}
|
||||
|
||||
public void Warning(string message)
|
||||
{
|
||||
this.logger.Warn(message);
|
||||
}
|
||||
|
||||
public void Info(string message)
|
||||
{
|
||||
this.logger.Info(message);
|
||||
}
|
||||
|
||||
public void Debug(string message)
|
||||
{
|
||||
this.logger.Debug(message);
|
||||
}
|
||||
|
||||
public void Error(string message)
|
||||
{
|
||||
this.logger.Error(message);
|
||||
}
|
||||
|
||||
public void Fatal(string message)
|
||||
{
|
||||
this.logger.Fatal(message);
|
||||
}
|
||||
|
||||
public void Trace(string message, params object[] args)
|
||||
{
|
||||
this.logger.Trace(message, args);
|
||||
}
|
||||
|
||||
public void Warning(string message, params object[] args)
|
||||
{
|
||||
this.logger.Warn(message, args);
|
||||
}
|
||||
|
||||
public void Info(string message, params object[] args)
|
||||
{
|
||||
this.logger.Info(message, args);
|
||||
}
|
||||
|
||||
public void Debug(string message, params object[] args)
|
||||
{
|
||||
this.logger.Debug(message, args);
|
||||
}
|
||||
|
||||
public void Error(string message, params object[] args)
|
||||
{
|
||||
this.logger.Error(message, args);
|
||||
}
|
||||
|
||||
public void Fatal(string message, params object[] args)
|
||||
{
|
||||
this.logger.Fatal(message, args);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bfdee72346bcdc149b84d5348ba2e350
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d7d79a71ec52a14eb7c9a98c74edf1c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public class ObjectPool: Singleton<ObjectPool>, ISingletonAwake
|
||||
{
|
||||
private Dictionary<Type, Queue<object>> pool;
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
this.pool = new Dictionary<Type, Queue<object>>();
|
||||
}
|
||||
}
|
||||
|
||||
public T Fetch<T>() where T: class
|
||||
{
|
||||
return this.Fetch(typeof (T)) as T;
|
||||
}
|
||||
|
||||
public object Fetch(Type type)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
Queue<object> queue = null;
|
||||
object o;
|
||||
if (!pool.TryGetValue(type, out queue))
|
||||
{
|
||||
o = Activator.CreateInstance(type);
|
||||
}
|
||||
else if (queue.Count == 0)
|
||||
{
|
||||
o = Activator.CreateInstance(type);
|
||||
}
|
||||
else
|
||||
{
|
||||
o = queue.Dequeue();
|
||||
}
|
||||
|
||||
if (o is IPool iPool)
|
||||
{
|
||||
iPool.IsFromPool = true;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
public void Recycle(object obj)
|
||||
{
|
||||
if (obj is IPool p)
|
||||
{
|
||||
if (!p.IsFromPool)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 防止多次入池
|
||||
p.IsFromPool = false;
|
||||
}
|
||||
|
||||
Type type = obj.GetType();
|
||||
|
||||
RecycleInner(type, obj);
|
||||
}
|
||||
|
||||
private void RecycleInner(Type type, object obj)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
Queue<object> queue = null;
|
||||
if (!pool.TryGetValue(type, out queue))
|
||||
{
|
||||
queue = new Queue<object>();
|
||||
pool.Add(type, queue);
|
||||
}
|
||||
|
||||
// 一种对象最大为1000个
|
||||
if (queue.Count > 1000)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
queue.Enqueue(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ce562c445bed294d9fa3a5ea333cb7b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/GameScripts/DotNet/Core/World/Module/Options.meta
Normal file
8
Assets/GameScripts/DotNet/Core/World/Module/Options.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0ae23f776aa91d40a8d8b6a21ba483f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,41 @@
|
||||
using CommandLine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public enum AppType
|
||||
{
|
||||
Server,
|
||||
Watcher, // 每台物理机一个守护进程,用来启动该物理机上的所有进程
|
||||
GameTool,
|
||||
ExcelExporter,
|
||||
Proto2CS,
|
||||
BenchmarkClient,
|
||||
BenchmarkServer,
|
||||
|
||||
Demo,
|
||||
LockStep,
|
||||
}
|
||||
|
||||
public class Options: Singleton<Options>
|
||||
{
|
||||
[Option("AppType", Required = false, Default = AppType.Server, HelpText = "AppType enum")]
|
||||
public AppType AppType { get; set; }
|
||||
|
||||
[Option("StartConfig", Required = false, Default = "StartConfig/Localhost")]
|
||||
public string StartConfig { get; set; }
|
||||
|
||||
[Option("Process", Required = false, Default = 1)]
|
||||
public int Process { get; set; }
|
||||
|
||||
[Option("Develop", Required = false, Default = 0, HelpText = "develop mode, 0正式 1开发 2压测")]
|
||||
public int Develop { get; set; }
|
||||
|
||||
[Option("LogLevel", Required = false, Default = 2)]
|
||||
public int LogLevel { get; set; }
|
||||
|
||||
[Option("Console", Required = false, Default = 0)]
|
||||
public int Console { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c6389212976f63749bf647192099cbab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
118
Assets/GameScripts/DotNet/Core/World/Singleton.cs
Normal file
118
Assets/GameScripts/DotNet/Core/World/Singleton.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public interface ISingleton: IDisposable
|
||||
{
|
||||
void Register();
|
||||
|
||||
}
|
||||
|
||||
public abstract class Singleton<T>: ISingleton where T: Singleton<T>, new()
|
||||
{
|
||||
protected bool isDisposed;
|
||||
|
||||
[StaticField]
|
||||
private static T instance;
|
||||
|
||||
public static T Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
private set
|
||||
{
|
||||
instance = value;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Register()
|
||||
{
|
||||
Instance = (T)this;
|
||||
}
|
||||
|
||||
public bool IsDisposed()
|
||||
{
|
||||
return this.isDisposed;
|
||||
}
|
||||
|
||||
protected virtual void Destroy()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (this.isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDisposed = true;
|
||||
|
||||
this.Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class SingletonLock<T>: ISingleton, ISingletonLoad where T: SingletonLock<T>, new()
|
||||
{
|
||||
private bool isDisposed;
|
||||
|
||||
[StaticField]
|
||||
private static T instance;
|
||||
|
||||
[StaticField]
|
||||
private static object lockObj = new();
|
||||
|
||||
public static T Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
private set
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
instance = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Register()
|
||||
{
|
||||
Instance = (T)this;
|
||||
}
|
||||
|
||||
public bool IsDisposed()
|
||||
{
|
||||
return this.isDisposed;
|
||||
}
|
||||
|
||||
protected virtual void Destroy()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (this.isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDisposed = true;
|
||||
|
||||
Instance = null;
|
||||
|
||||
this.Destroy();
|
||||
}
|
||||
|
||||
public abstract void Load();
|
||||
}
|
||||
}
|
11
Assets/GameScripts/DotNet/Core/World/Singleton.cs.meta
Normal file
11
Assets/GameScripts/DotNet/Core/World/Singleton.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 101c23054b29c3247bcfcedb81ff4d38
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
109
Assets/GameScripts/DotNet/Core/World/World.cs
Normal file
109
Assets/GameScripts/DotNet/Core/World/World.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ET
|
||||
{
|
||||
public class World: IDisposable
|
||||
{
|
||||
[StaticField]
|
||||
private static World instance;
|
||||
|
||||
public static World Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance ??= new World();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Stack<Type> stack = new();
|
||||
private readonly Dictionary<Type, ISingleton> singletons = new();
|
||||
|
||||
private World()
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
instance = null;
|
||||
|
||||
lock (this)
|
||||
{
|
||||
while (this.stack.Count > 0)
|
||||
{
|
||||
Type type = this.stack.Pop();
|
||||
this.singletons[type].Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T AddSingleton<T>(bool replace = false) where T : class, ISingleton, ISingletonAwake, new()
|
||||
{
|
||||
T singleton = new();
|
||||
singleton.Awake();
|
||||
|
||||
AddSingleton(singleton, replace);
|
||||
return singleton;
|
||||
}
|
||||
|
||||
public T AddSingleton<T, A>(A a, bool replace = false) where T : class, ISingleton, ISingletonAwake<A>, new()
|
||||
{
|
||||
T singleton = new();
|
||||
singleton.Awake(a);
|
||||
|
||||
AddSingleton(singleton, replace);
|
||||
return singleton;
|
||||
}
|
||||
|
||||
public T AddSingleton<T, A, B>(A a, B b, bool replace = false) where T : class, ISingleton, ISingletonAwake<A, B>, new()
|
||||
{
|
||||
T singleton = new();
|
||||
singleton.Awake(a, b);
|
||||
|
||||
AddSingleton(singleton, replace);
|
||||
return singleton;
|
||||
}
|
||||
|
||||
public T AddSingleton<T, A, B, C>(A a, B b, C c, bool replace = false) where T : class, ISingleton, ISingletonAwake<A, B, C>, new()
|
||||
{
|
||||
T singleton = new();
|
||||
singleton.Awake(a, b, c);
|
||||
|
||||
AddSingleton(singleton, replace);
|
||||
return singleton;
|
||||
}
|
||||
|
||||
public void AddSingleton(ISingleton singleton, bool replace = false)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
Type type = singleton.GetType();
|
||||
if (!replace)
|
||||
{
|
||||
this.stack.Push(type);
|
||||
}
|
||||
singletons[type] = singleton;
|
||||
}
|
||||
|
||||
singleton.Register();
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
foreach (Type type in this.stack)
|
||||
{
|
||||
ISingleton singleton = this.singletons[type];
|
||||
|
||||
if (singleton is not ISingletonLoad iSingletonLoad)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
iSingletonLoad.Load();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/GameScripts/DotNet/Core/World/World.cs.meta
Normal file
11
Assets/GameScripts/DotNet/Core/World/World.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 631ee3459f804c34f8662d64e91a65de
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user