添加加载项热更新功能 (#55)

* 添加加载项热更新功能

* 补上连接事件触发代码
This commit is contained in:
milimoe 2026-02-06 09:57:42 +08:00 committed by GitHub
parent 39280c3dc5
commit 3d2cf46631
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 236 additions and 31 deletions

View File

@ -239,8 +239,8 @@ namespace Milimoe.FunGame.Server.Controller
}
else eventArgs.Success = false;
FunGameSystem.ServerPluginLoader?.OnBeforeLogoutEvent(this, eventArgs);
FunGameSystem.WebAPIPluginLoader?.OnBeforeLogoutEvent(this, eventArgs);
FunGameSystem.ServerPluginLoader?.OnAfterLogoutEvent(this, eventArgs);
FunGameSystem.WebAPIPluginLoader?.OnAfterLogoutEvent(this, eventArgs);
}
resultData.Add("msg", msg);
resultData.Add("key", key);

View File

@ -1,4 +1,5 @@
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Library.Common.Event;
using Milimoe.FunGame.Core.Library.Common.Network;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Server.Controller;
@ -168,21 +169,34 @@ void StartServerListening()
bool isDebugMode = false;
// 开始处理客户端连接请求
ConnectEventArgs eventArgs = new(clientip, Config.ServerPort);
FunGameSystem.ServerPluginLoader?.OnBeforeConnectEvent(socket, eventArgs);
FunGameSystem.WebAPIPluginLoader?.OnBeforeConnectEvent(socket, eventArgs);
SocketObject[] objs = socket.Receive();
(isConnected, isDebugMode) = await ConnectController.Connect(listener, socket, token, clientip, objs);
eventArgs.Success = isConnected;
if (isConnected)
{
eventArgs.ConnectResult = ConnectResult.Success;
FunGameSystem.ServerPluginLoader?.OnAfterConnectEvent(socket, eventArgs);
FunGameSystem.WebAPIPluginLoader?.OnAfterConnectEvent(socket, eventArgs);
ServerModel<ServerSocket> ClientModel = new(listener, socket, isDebugMode);
ClientModel.SetClientName(clientip);
Task t = Task.Run(ClientModel.Start);
}
else
{
eventArgs.ConnectResult = ConnectResult.ConnectFailed;
FunGameSystem.ServerPluginLoader?.OnAfterConnectEvent(socket, eventArgs);
FunGameSystem.WebAPIPluginLoader?.OnAfterConnectEvent(socket, eventArgs);
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 连接失败。", InvokeMessageType.Core);
}
Config.ConnectingPlayerCount--;
}).OnError(e =>
{
ConnectEventArgs eventArgs = new(clientip, Config.ServerPort, ConnectResult.CanNotConnect);
FunGameSystem.ServerPluginLoader?.OnAfterConnectEvent(socket, eventArgs);
FunGameSystem.WebAPIPluginLoader?.OnAfterConnectEvent(socket, eventArgs);
if (--Config.ConnectingPlayerCount < 0) Config.ConnectingPlayerCount = 0;
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 中断连接!", InvokeMessageType.Core);
ServerHelper.Error(e);
@ -232,26 +246,39 @@ void StartServerListening()
bool isDebugMode = false;
// 开始处理客户端连接请求
ConnectEventArgs eventArgs = new(clientip, Config.ServerPort);
FunGameSystem.ServerPluginLoader?.OnBeforeConnectEvent(socket, eventArgs);
FunGameSystem.WebAPIPluginLoader?.OnBeforeConnectEvent(socket, eventArgs);
IEnumerable<SocketObject> objs = [];
while (!objs.Any(o => o.SocketType == SocketMessageType.Connect))
{
objs = await socket.ReceiveAsync();
}
(isConnected, isDebugMode) = await ConnectController.Connect(listener, socket, token, clientip, objs.Where(o => o.SocketType == SocketMessageType.Connect));
eventArgs.Success = isConnected;
if (isConnected)
{
eventArgs.ConnectResult = ConnectResult.Success;
FunGameSystem.ServerPluginLoader?.OnAfterConnectEvent(socket, eventArgs);
FunGameSystem.WebAPIPluginLoader?.OnAfterConnectEvent(socket, eventArgs);
ServerModel<ServerWebSocket> ClientModel = new(listener, socket, isDebugMode);
ClientModel.SetClientName(clientip);
Task t = Task.Run(ClientModel.Start);
}
else
{
eventArgs.ConnectResult = ConnectResult.ConnectFailed;
FunGameSystem.ServerPluginLoader?.OnAfterConnectEvent(socket, eventArgs);
FunGameSystem.WebAPIPluginLoader?.OnAfterConnectEvent(socket, eventArgs);
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 连接失败。", InvokeMessageType.Core);
await socket.CloseAsync();
}
Config.ConnectingPlayerCount--;
}).OnError(e =>
{
ConnectEventArgs eventArgs = new(clientip, Config.ServerPort, ConnectResult.CanNotConnect);
FunGameSystem.ServerPluginLoader?.OnAfterConnectEvent(socket, eventArgs);
FunGameSystem.WebAPIPluginLoader?.OnAfterConnectEvent(socket, eventArgs);
if (--Config.ConnectingPlayerCount < 0) Config.ConnectingPlayerCount = 0;
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 中断连接!", InvokeMessageType.Core);
ServerHelper.Error(e);

View File

@ -51,6 +51,31 @@ namespace Milimoe.FunGame.Server.Model
case OrderDictionary.ShowUsers2:
ShowUsers(server);
break;
case OrderDictionary.ReloadAddons:
FunGameSystem.HotReloadServerPlugins();
FunGameSystem.HotReloadWebAPIPlugins();
FunGameSystem.HotReloadGameModuleList();
break;
case OrderDictionary.ReloadPlugins1:
FunGameSystem.HotReloadServerPlugins();
FunGameSystem.HotReloadWebAPIPlugins();
break;
case OrderDictionary.ReloadPlugins2:
FunGameSystem.HotReloadServerPlugins();
FunGameSystem.HotReloadWebAPIPlugins();
break;
case OrderDictionary.ReloadPlugins3:
FunGameSystem.HotReloadServerPlugins();
break;
case OrderDictionary.ReloadPlugins4:
FunGameSystem.HotReloadWebAPIPlugins();
break;
case OrderDictionary.ReloadModules1:
FunGameSystem.HotReloadGameModuleList();
break;
case OrderDictionary.ReloadModules2:
FunGameSystem.HotReloadGameModuleList();
break;
default:
break;
}

View File

@ -664,6 +664,9 @@ namespace Milimoe.FunGame.Server.Model
{
try
{
GeneralEventArgs eventArgs = new();
FunGameSystem.ServerPluginLoader?.OnAfterDisconnectEvent(this, eventArgs);
FunGameSystem.WebAPIPluginLoader?.OnAfterDisconnectEvent(this, eventArgs);
await Socket.CloseAsync();
_running = false;
Listener.ClientList.Remove(ClientName);

View File

@ -1,4 +1,5 @@
using System.Text;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Server.Others
@ -10,6 +11,19 @@ namespace Milimoe.FunGame.Server.Others
/// </summary>
public static bool AspNetCore { get; set; } = false;
/// <summary>
/// 使用可热更新的加载项模式
/// </summary>
public static bool UseHotLoadAddons
{
get
{
_useHotLoadAddon ??= Convert.ToBoolean(INIHelper.ReadINI("Console", "UseHotLoadAddons"));
return _useHotLoadAddon.Value;
}
}
private static bool? _useHotLoadAddon = null;
/// <summary>
/// 日志级别
/// </summary>

View File

@ -7,16 +7,23 @@
public const string Exit = "exit";
public const string Close = "close";
public const string Restart = "restart";
public const string AddBanned1 = "ban -add";
public const string AddBanned1 = "ban add";
public const string AddBanned2 = "ban -a";
public const string RemoveBanned1 = "ban -remove";
public const string RemoveBanned1 = "ban remove";
public const string RemoveBanned2 = "ban -r";
public const string Kick = "kick";
public const string Logout = "logout";
public const string ShowList = "showlist";
public const string ShowClients1 = "showclients";
public const string ShowClients1 = "show clients";
public const string ShowClients2 = "clients";
public const string ShowUsers1 = "showusers";
public const string ShowUsers1 = "show users";
public const string ShowUsers2 = "users";
public const string ReloadAddons = "reload addons";
public const string ReloadPlugins1 = "reload plugins";
public const string ReloadPlugins2 = "reload -p";
public const string ReloadPlugins3 = "reload -sp";
public const string ReloadPlugins4 = "reload -wp";
public const string ReloadModules1 = "reload modules";
public const string ReloadModules2 = "reload -m";
}
}

View File

@ -159,7 +159,34 @@ namespace Milimoe.FunGame.Server.Services
// 读取modules目录下的模组
try
{
GameModuleLoader = GameModuleLoader.LoadGameModules(Config.FunGameType, delegates);
if (Config.UseHotLoadAddons && GameModuleLoader != null)
{
GameModuleLoader.HotReload(Config.FunGameType, delegates);
}
else
{
GameModuleLoader = Config.UseHotLoadAddons ? GameModuleLoader.LoadGameModulesByHotLoadMode(Config.FunGameType, delegates) : GameModuleLoader.LoadGameModules(Config.FunGameType, delegates);
}
foreach (GameMap map in GameModuleLoader.Maps.Values)
{
supported.Add(map.Name);
ServerHelper.WriteLine("GameMap Loaded -> " + map.Name, InvokeMessageType.Core);
}
foreach (CharacterModule module in GameModuleLoader.Characters.Values)
{
supported.Add(module.Name);
ServerHelper.WriteLine("CharacterModule Loaded -> " + module.Name, InvokeMessageType.Core);
}
foreach (SkillModule module in GameModuleLoader.Skills.Values)
{
supported.Add(module.Name);
ServerHelper.WriteLine("SkillModule Loaded -> " + module.Name, InvokeMessageType.Core);
}
foreach (ItemModule module in GameModuleLoader.Items.Values)
{
supported.Add(module.Name);
ServerHelper.WriteLine("ItemModule Loaded -> " + module.Name, InvokeMessageType.Core);
}
foreach (GameModuleServer module in GameModuleLoader.ModuleServers.Values)
{
try
@ -173,7 +200,7 @@ namespace Milimoe.FunGame.Server.Services
}
if (check)
{
if (!module.IsAnonymous) supported.Add(module.Name);
supported.Add(module.Name);
ServerHelper.WriteLine("GameModule Loaded -> " + module.Name, InvokeMessageType.Core);
}
}
@ -193,6 +220,31 @@ namespace Milimoe.FunGame.Server.Services
return Config.GameModuleSupported.Length > 0;
}
/// <summary>
/// 热更新游戏模组
/// </summary>
/// <returns></returns>
public static bool HotReloadGameModuleList()
{
if (!Config.UseHotLoadAddons)
{
return false;
}
if (GameModuleLoader != null)
{
GameModuleLoader.Modules.Clear();
GameModuleLoader.ModuleServers.Clear();
GameModuleLoader.Maps.Clear();
GameModuleLoader.Characters.Clear();
GameModuleLoader.Skills.Clear();
GameModuleLoader.Items.Clear();
GameModuleLoader = null;
}
return GetGameModuleList();
}
/// <summary>
/// 加载服务器插件
/// </summary>
@ -204,7 +256,14 @@ namespace Milimoe.FunGame.Server.Services
try
{
// 读取plugins目录下的插件
ServerPluginLoader = ServerPluginLoader.LoadPlugins(delegates);
if (Config.UseHotLoadAddons && ServerPluginLoader != null)
{
ServerPluginLoader.HotReload(delegates);
}
else
{
ServerPluginLoader = Config.UseHotLoadAddons ? ServerPluginLoader.LoadPluginsByHotLoadMode(delegates) : ServerPluginLoader.LoadPlugins(delegates);
}
foreach (ServerPlugin plugin in ServerPluginLoader.Plugins.Values)
{
ServerHelper.WriteLine("Plugin Loaded -> " + plugin.Name, InvokeMessageType.Core);
@ -227,7 +286,14 @@ namespace Milimoe.FunGame.Server.Services
try
{
// 读取plugins目录下的插件
WebAPIPluginLoader = WebAPIPluginLoader.LoadPlugins(delegates, otherobjs);
if (Config.UseHotLoadAddons && WebAPIPluginLoader != null)
{
WebAPIPluginLoader.HotReload(delegates, otherobjs);
}
else
{
WebAPIPluginLoader = Config.UseHotLoadAddons ? WebAPIPluginLoader.LoadPluginsByHotLoadMode(delegates, otherobjs) : WebAPIPluginLoader.LoadPlugins(delegates, otherobjs);
}
foreach (WebAPIPlugin plugin in WebAPIPluginLoader.Plugins.Values)
{
ServerHelper.WriteLine("Plugin Loaded -> " + plugin.Name, InvokeMessageType.Core);
@ -239,6 +305,36 @@ namespace Milimoe.FunGame.Server.Services
}
}
/// <summary>
/// 热更新服务器插件
/// </summary>
/// <returns></returns>
public static bool HotReloadServerPlugins()
{
if (!Config.UseHotLoadAddons)
{
return false;
}
GetServerPlugins();
return (ServerPluginLoader?.Plugins.Count ?? 0) > 0;
}
/// <summary>
/// 热更新 Web API 插件
/// </summary>
/// <returns></returns>
public static bool HotReloadWebAPIPlugins()
{
if (!Config.UseHotLoadAddons)
{
return false;
}
GetWebAPIPlugins();
return (WebAPIPluginLoader?.Plugins.Count ?? 0) > 0;
}
/// <summary>
/// Web API 启动完成回调
/// </summary>

View File

@ -4,6 +4,7 @@ using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Server.Others;
using TaskScheduler = Milimoe.FunGame.Core.Api.Utility.TaskScheduler;
namespace Milimoe.FunGame.Server.Services
{
@ -71,6 +72,12 @@ namespace Milimoe.FunGame.Server.Services
public static void Error(Exception e)
{
Console.WriteLine("\r" + GetPrefix(InvokeMessageType.Error, LogLevel.Error) + e.Message + "\n" + e.StackTrace);
if (e.InnerException != null)
{
Error(e.InnerException);
TXTHelper.AppendErrorLog(e);
return;
}
Type();
TXTHelper.AppendErrorLog(e);
}
@ -227,6 +234,11 @@ namespace Milimoe.FunGame.Server.Services
if (MaxConnectFailed != null) Config.MaxConnectionFaileds = (int)MaxConnectFailed;
}
WriteLine($"当前输出的日志级别为:{Config.LogLevelValue}", useLevel: false);
if (Config.UseHotLoadAddons)
{
WriteLine($"已启用可热更新加载项的加载模式", useLevel: false);
TaskScheduler.StartCleanUnusedAddonContexts(Error);
}
}
catch (Exception e)
{

View File

@ -15,6 +15,7 @@ using Milimoe.FunGame;
using Milimoe.FunGame.Core.Api.Transmittal;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Common.Event;
using Milimoe.FunGame.Core.Library.Common.Network;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Server.Controller;
@ -320,28 +321,50 @@ async Task WebSocketConnectionHandler(HttpContext context)
Guid token = Guid.NewGuid();
ServerWebSocket socket = new(listener, instance, clientip, clientip, token);
Config.ConnectingPlayerCount++;
bool isConnected = false;
bool isDebugMode = false;
try
{
bool isConnected = false;
bool isDebugMode = false;
// 开始处理客户端连接请求
IEnumerable<SocketObject> objs = [];
while (!objs.Any(o => o.SocketType == SocketMessageType.Connect))
{
objs = await socket.ReceiveAsync();
// 开始处理客户端连接请求
ConnectEventArgs eventArgs = new(clientip, Config.ServerPort);
FunGameSystem.ServerPluginLoader?.OnBeforeConnectEvent(socket, eventArgs);
FunGameSystem.WebAPIPluginLoader?.OnBeforeConnectEvent(socket, eventArgs);
IEnumerable<SocketObject> objs = [];
while (!objs.Any(o => o.SocketType == SocketMessageType.Connect))
{
objs = await socket.ReceiveAsync();
}
(isConnected, isDebugMode) = await ConnectController.Connect(listener, socket, token, clientip, objs.Where(o => o.SocketType == SocketMessageType.Connect));
eventArgs.Success = isConnected;
if (isConnected)
{
eventArgs.ConnectResult = ConnectResult.Success;
FunGameSystem.ServerPluginLoader?.OnAfterConnectEvent(socket, eventArgs);
FunGameSystem.WebAPIPluginLoader?.OnAfterConnectEvent(socket, eventArgs);
ServerModel<ServerWebSocket> ClientModel = new(listener, socket, isDebugMode);
ClientModel.SetClientName(clientip);
await ClientModel.Start();
}
else
{
eventArgs.ConnectResult = ConnectResult.ConnectFailed;
FunGameSystem.ServerPluginLoader?.OnAfterConnectEvent(socket, eventArgs);
FunGameSystem.WebAPIPluginLoader?.OnAfterConnectEvent(socket, eventArgs);
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 连接失败。", InvokeMessageType.Core);
await socket.CloseAsync();
}
Config.ConnectingPlayerCount--;
}
(isConnected, isDebugMode) = await ConnectController.Connect(listener, socket, token, clientip, objs.Where(o => o.SocketType == SocketMessageType.Connect));
if (isConnected)
catch (Exception e)
{
ServerModel<ServerWebSocket> ClientModel = new(listener, socket, isDebugMode);
ClientModel.SetClientName(clientip);
await ClientModel.Start();
ConnectEventArgs eventArgs = new(clientip, Config.ServerPort, ConnectResult.CanNotConnect);
FunGameSystem.ServerPluginLoader?.OnAfterConnectEvent(context, eventArgs);
FunGameSystem.WebAPIPluginLoader?.OnAfterConnectEvent(context, eventArgs);
if (--Config.ConnectingPlayerCount < 0) Config.ConnectingPlayerCount = 0;
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 中断连接!", InvokeMessageType.Core);
ServerHelper.Error(e);
}
else
{
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 连接失败。", InvokeMessageType.Core);
await socket.CloseAsync();
}
Config.ConnectingPlayerCount--;
}
else
{
@ -350,8 +373,6 @@ async Task WebSocketConnectionHandler(HttpContext context)
}
catch (Exception e)
{
if (--Config.ConnectingPlayerCount < 0) Config.ConnectingPlayerCount = 0;
ServerHelper.WriteLine(ServerHelper.MakeClientName(clientip) + " 中断连接!", InvokeMessageType.Core);
ServerHelper.Error(e);
}
}

View File

@ -1,6 +1,6 @@
# 项目简介
FunGameServer 是 [FunGame](https://github.com/project-redbud/FunGame-Core) 的服务器端实现,基于 ASP.NET Core Web API轻量、高性能、跨平台。
FunGameServer 是 [FunGame](https://github.com/project-redbud/FunGame-Core) 的服务器端实现,基于 ASP.NET Core Web API高性能且跨平台。
它支持多种服务模式,使得服务器端的扩展和集成更加灵活: