revert d671e4d311
revert Merge pull request '聊天系统' (#13) from qbb0530/whale-town-front:main into main Reviewed-on: #13
This commit is contained in:
@@ -57,15 +57,4 @@ const GRID_SNAP_REQUESTED = "grid_snap_requested"
|
||||
# ============================================================================
|
||||
# 测试事件
|
||||
# ============================================================================
|
||||
const TEST_EVENT = "test_event"
|
||||
|
||||
# ============================================================================
|
||||
# 聊天事件
|
||||
# ============================================================================
|
||||
const CHAT_MESSAGE_SENT = "chat_message_sent"
|
||||
const CHAT_MESSAGE_RECEIVED = "chat_message_received"
|
||||
const CHAT_ERROR_OCCURRED = "chat_error_occurred"
|
||||
const CHAT_CONNECTION_STATE_CHANGED = "chat_connection_state_changed"
|
||||
const CHAT_POSITION_UPDATED = "chat_position_updated"
|
||||
const CHAT_LOGIN_SUCCESS = "chat_login_success"
|
||||
const CHAT_LOGIN_FAILED = "chat_login_failed"
|
||||
const TEST_EVENT = "test_event"
|
||||
@@ -80,197 +80,19 @@ var current_email: String = ""
|
||||
# 网络请求管理
|
||||
var active_request_ids: Array = []
|
||||
|
||||
# ============ Token 管理 ============
|
||||
|
||||
# 本地存储路径常量
|
||||
const AUTH_CONFIG_PATH: String = "user://auth.cfg"
|
||||
|
||||
# Token 存储(内存中,用于快速访问)
|
||||
var _access_token: String = "" # JWT访问令牌(短期,用于API和WebSocket)
|
||||
var _refresh_token: String = "" # JWT刷新令牌(长期,用于获取新access_token)
|
||||
var _user_info: Dictionary = {} # 用户信息
|
||||
var _token_expiry: float = 0.0 # access_token过期时间(Unix时间戳)
|
||||
|
||||
# 游戏 token(兼容旧代码,保留但标记为废弃)
|
||||
var _game_token: String = "" # @deprecated 使用 _access_token 替代
|
||||
|
||||
# ============ 生命周期方法 ============
|
||||
|
||||
# 初始化管理器
|
||||
func _init() -> void:
|
||||
func _init():
|
||||
print("AuthManager 初始化完成")
|
||||
_load_auth_data()
|
||||
|
||||
# 清理资源
|
||||
func cleanup() -> void:
|
||||
func cleanup():
|
||||
# 取消所有活动的网络请求
|
||||
for request_id in active_request_ids:
|
||||
NetworkManager.cancel_request(request_id)
|
||||
active_request_ids.clear()
|
||||
|
||||
# ============ Token 管理 ============
|
||||
|
||||
# 保存 Token 到内存
|
||||
#
|
||||
# 参数:
|
||||
# data: Dictionary - 登录响应数据
|
||||
#
|
||||
# 功能:
|
||||
# - 从登录响应中提取 access_token 和 refresh_token
|
||||
# - 保存到内存变量中
|
||||
# - 保存用户信息
|
||||
func _save_tokens_to_memory(data: Dictionary) -> void:
|
||||
if not data.has("data"):
|
||||
print("⚠️ 登录响应中没有 data 字段")
|
||||
return
|
||||
|
||||
var token_data: Dictionary = data.data
|
||||
_access_token = token_data.get("access_token", "")
|
||||
_refresh_token = token_data.get("refresh_token", "")
|
||||
_user_info = token_data.get("user", {})
|
||||
_token_expiry = Time.get_unix_time_from_system() + float(token_data.get("expires_in", 0))
|
||||
|
||||
# 保持兼容性:设置 _game_token
|
||||
_game_token = _access_token
|
||||
|
||||
print("✅ Token已保存到内存")
|
||||
print(" Access Token: ", _access_token.substr(0, 20) + "...")
|
||||
print(" 用户: ", _user_info.get("username", "未知"))
|
||||
|
||||
# 保存 Token 到本地(ConfigFile)
|
||||
#
|
||||
# 参数:
|
||||
# data: Dictionary - 登录响应数据
|
||||
#
|
||||
# 功能:
|
||||
# - 将 refresh_token 和用户信息保存到 ConfigFile
|
||||
# - access_token 不保存到本地,仅保存在内存中
|
||||
func _save_tokens_to_local(data: Dictionary) -> void:
|
||||
if not data.has("data"):
|
||||
return
|
||||
|
||||
var token_data: Dictionary = data.data
|
||||
var auth_data: Dictionary = {
|
||||
"refresh_token": token_data.get("refresh_token", ""),
|
||||
"user_id": token_data.get("user", {}).get("id", ""),
|
||||
"username": token_data.get("user", {}).get("username", ""),
|
||||
"saved_at": Time.get_unix_time_from_system()
|
||||
}
|
||||
|
||||
var config: ConfigFile = ConfigFile.new()
|
||||
config.load(AUTH_CONFIG_PATH)
|
||||
config.set_value("auth", "refresh_token", auth_data["refresh_token"])
|
||||
config.set_value("auth", "user_id", auth_data["user_id"])
|
||||
config.set_value("auth", "username", auth_data["username"])
|
||||
config.set_value("auth", "saved_at", auth_data["saved_at"])
|
||||
|
||||
var error: Error = config.save(AUTH_CONFIG_PATH)
|
||||
if error == OK:
|
||||
print("✅ Token已保存到本地: ", AUTH_CONFIG_PATH)
|
||||
else:
|
||||
print("❌ 保存Token到本地失败,错误码: ", error)
|
||||
|
||||
# 从本地加载 Token(游戏启动时调用)
|
||||
#
|
||||
# 功能:
|
||||
# - 从 ConfigFile 加载 refresh_token 和用户信息
|
||||
# - access_token 需要通过 refresh_token 刷新获取
|
||||
func _load_auth_data() -> void:
|
||||
if not FileAccess.file_exists(AUTH_CONFIG_PATH):
|
||||
print("ℹ️ 本地不存在认证数据")
|
||||
return
|
||||
|
||||
var config: ConfigFile = ConfigFile.new()
|
||||
var error: Error = config.load(AUTH_CONFIG_PATH)
|
||||
|
||||
if error != OK:
|
||||
print("❌ 加载本地认证数据失败,错误码: ", error)
|
||||
return
|
||||
|
||||
_refresh_token = config.get_value("auth", "refresh_token", "")
|
||||
var user_id: String = config.get_value("auth", "user_id", "")
|
||||
var username: String = config.get_value("auth", "username", "")
|
||||
|
||||
if not _refresh_token.is_empty():
|
||||
_user_info = {
|
||||
"id": user_id,
|
||||
"username": username
|
||||
}
|
||||
print("✅ 已从本地加载认证数据")
|
||||
print(" 用户: ", username)
|
||||
else:
|
||||
print("⚠️ 本地认证数据无效(没有 refresh_token)")
|
||||
|
||||
# 清除本地认证数据(登出时调用)
|
||||
#
|
||||
# 功能:
|
||||
# - 清除内存中的 Token
|
||||
# - 删除本地 ConfigFile
|
||||
func _clear_auth_data() -> void:
|
||||
_access_token = ""
|
||||
_refresh_token = ""
|
||||
_user_info = {}
|
||||
_token_expiry = 0.0
|
||||
_game_token = ""
|
||||
|
||||
if FileAccess.file_exists(AUTH_CONFIG_PATH):
|
||||
DirAccess.remove_absolute(AUTH_CONFIG_PATH)
|
||||
print("✅ 已清除本地认证数据")
|
||||
|
||||
# ============ Token 访问方法 ============
|
||||
|
||||
# 设置游戏 token(兼容旧代码,推荐使用 _save_tokens_to_memory)
|
||||
#
|
||||
# 参数:
|
||||
# token: String - 游戏认证 token
|
||||
#
|
||||
# 使用场景:
|
||||
# - 登录成功后设置 token
|
||||
# - 从服务器响应中获取 token
|
||||
func set_game_token(token: String) -> void:
|
||||
_game_token = token
|
||||
_access_token = token # 同步更新 access_token
|
||||
print("AuthManager: 游戏 token 已设置")
|
||||
|
||||
# 获取游戏 token
|
||||
#
|
||||
# 返回值:
|
||||
# String - access_token(如果未设置则返回空字符串)
|
||||
#
|
||||
# 使用场景:
|
||||
# - ChatManager 连接 WebSocket 时需要 token
|
||||
# - 其他需要游戏认证的场景
|
||||
func get_game_token() -> String:
|
||||
return _access_token
|
||||
|
||||
# 获取 access token
|
||||
#
|
||||
# 返回值:
|
||||
# String - JWT访问令牌
|
||||
#
|
||||
# 使用场景:
|
||||
# - API请求认证
|
||||
# - WebSocket聊天认证
|
||||
func get_access_token() -> String:
|
||||
return _access_token
|
||||
|
||||
# 获取 refresh token
|
||||
#
|
||||
# 返回值:
|
||||
# String - JWT刷新令牌
|
||||
#
|
||||
# 使用场景:
|
||||
# - 刷新过期的 access token
|
||||
func get_refresh_token() -> String:
|
||||
return _refresh_token
|
||||
|
||||
# 获取用户信息
|
||||
#
|
||||
# 返回值:
|
||||
# Dictionary - 用户信息字典
|
||||
func get_user_info() -> Dictionary:
|
||||
return _user_info
|
||||
|
||||
# ============ 登录相关方法 ============
|
||||
|
||||
# 执行密码登录
|
||||
@@ -599,21 +421,19 @@ func validate_verification_code(code: String) -> Dictionary:
|
||||
# ============ 网络响应处理 ============
|
||||
|
||||
# 处理登录响应
|
||||
func _on_login_response(success: bool, data: Dictionary, error_info: Dictionary) -> void:
|
||||
func _on_login_response(success: bool, data: Dictionary, error_info: Dictionary):
|
||||
_reset_login_state()
|
||||
|
||||
|
||||
var result = ResponseHandler.handle_login_response(success, data, error_info)
|
||||
|
||||
|
||||
if result.should_show_toast:
|
||||
show_toast_message.emit(result.message, result.success)
|
||||
|
||||
|
||||
if result.success:
|
||||
# 保存 Token 到内存和本地
|
||||
_save_tokens_to_memory(data)
|
||||
_save_tokens_to_local(data)
|
||||
|
||||
var username: String = _user_info.get("username", "")
|
||||
|
||||
var username = ""
|
||||
if data.has("data") and data.data.has("user") and data.data.user.has("username"):
|
||||
username = data.data.user.username
|
||||
|
||||
# 延迟发送登录成功信号
|
||||
await Engine.get_main_loop().create_timer(1.0).timeout
|
||||
login_success.emit(username)
|
||||
@@ -621,21 +441,19 @@ func _on_login_response(success: bool, data: Dictionary, error_info: Dictionary)
|
||||
login_failed.emit(result.message)
|
||||
|
||||
# 处理验证码登录响应
|
||||
func _on_verification_login_response(success: bool, data: Dictionary, error_info: Dictionary) -> void:
|
||||
func _on_verification_login_response(success: bool, data: Dictionary, error_info: Dictionary):
|
||||
_reset_login_state()
|
||||
|
||||
|
||||
var result = ResponseHandler.handle_verification_code_login_response(success, data, error_info)
|
||||
|
||||
|
||||
if result.should_show_toast:
|
||||
show_toast_message.emit(result.message, result.success)
|
||||
|
||||
|
||||
if result.success:
|
||||
# 保存 Token 到内存和本地
|
||||
_save_tokens_to_memory(data)
|
||||
_save_tokens_to_local(data)
|
||||
|
||||
var username: String = _user_info.get("username", "")
|
||||
|
||||
var username = ""
|
||||
if data.has("data") and data.data.has("user") and data.data.user.has("username"):
|
||||
username = data.data.user.username
|
||||
|
||||
await Engine.get_main_loop().create_timer(1.0).timeout
|
||||
login_success.emit(username)
|
||||
else:
|
||||
|
||||
@@ -1,678 +0,0 @@
|
||||
extends Node
|
||||
|
||||
# ============================================================================
|
||||
# ChatManager.gd - 聊天系统业务逻辑核心
|
||||
# ============================================================================
|
||||
# 管理聊天功能的核心业务逻辑
|
||||
#
|
||||
# 核心职责:
|
||||
# - 聊天消息发送/接收协调
|
||||
# - 客户端频率限制(10条/分钟)
|
||||
# - 消息历史管理(最多100条)
|
||||
# - Signal Up: 通过信号和 EventSystem 向上通知
|
||||
# - 整合 AuthManager 获取 token
|
||||
#
|
||||
# 使用方式:
|
||||
# ChatManager.connect_to_chat_server()
|
||||
# ChatManager.send_chat_message("Hello", "local")
|
||||
# ChatManager.chat_message_received.connect(_on_message_received)
|
||||
#
|
||||
# 注意事项:
|
||||
# - 作为自动加载单例,全局可访问
|
||||
# - 遵循 "Signal Up, Call Down" 架构
|
||||
# - 所有聊天事件通过 EventSystem 广播
|
||||
# ============================================================================
|
||||
|
||||
# ============================================================================
|
||||
# 信号定义 (Signal Up)
|
||||
# ============================================================================
|
||||
|
||||
# 聊天消息已发送信号
|
||||
# 参数:
|
||||
# message_id: String - 消息 ID
|
||||
# timestamp: float - 时间戳
|
||||
signal chat_message_sent(message_id: String, timestamp: float)
|
||||
|
||||
# 聊天消息已接收信号
|
||||
# 参数:
|
||||
# from_user: String - 发送者用户名
|
||||
# content: String - 消息内容
|
||||
# show_bubble: bool - 是否显示气泡
|
||||
# timestamp: float - 时间戳
|
||||
signal chat_message_received(from_user: String, content: String, show_bubble: bool, timestamp: float)
|
||||
|
||||
# 聊天错误发生信号
|
||||
# 参数:
|
||||
# error_code: String - 错误代码
|
||||
# message: String - 错误消息
|
||||
signal chat_error_occurred(error_code: String, message: String)
|
||||
|
||||
# 聊天连接状态变化信号
|
||||
# 参数:
|
||||
# state: int - 连接状态(0=DISCONNECTED, 1=CONNECTING, 2=CONNECTED, 3=RECONNECTING, 4=ERROR)
|
||||
signal chat_connection_state_changed(state: int)
|
||||
|
||||
# 位置更新成功信号
|
||||
# 参数:
|
||||
# stream: String - Stream 名称
|
||||
# topic: String - Topic 名称
|
||||
signal chat_position_updated(stream: String, topic: String)
|
||||
|
||||
# ============================================================================
|
||||
# 常量定义
|
||||
# ============================================================================
|
||||
|
||||
# WebSocket 服务器 URL(原生 WebSocket)
|
||||
const WEBSOCKET_URL: String = "wss://whaletownend.xinghangee.icu/game"
|
||||
|
||||
# 重连配置
|
||||
const RECONNECT_MAX_ATTEMPTS: int = 5
|
||||
const RECONNECT_BASE_DELAY: float = 3.0
|
||||
|
||||
# 频率限制配置
|
||||
const RATE_LIMIT_MESSAGES: int = 10
|
||||
const RATE_LIMIT_WINDOW: float = 60.0 # 秒
|
||||
|
||||
# 消息限制
|
||||
const MAX_MESSAGE_LENGTH: int = 1000
|
||||
|
||||
# 当前会话消息限制(当前游戏会话,超过后删除最旧的)
|
||||
const MAX_SESSION_MESSAGES: int = 100
|
||||
|
||||
# 历史消息分页大小(从 Zulip 后端每次加载的数量)
|
||||
const HISTORY_PAGE_SIZE: int = 100
|
||||
|
||||
# 错误消息映射
|
||||
const CHAT_ERROR_MESSAGES: Dictionary = {
|
||||
"AUTH_FAILED": "聊天认证失败,请重新登录",
|
||||
"RATE_LIMIT": "消息发送过于频繁,请稍后再试",
|
||||
"CONTENT_FILTERED": "消息内容包含违规内容",
|
||||
"CONTENT_TOO_LONG": "消息内容过长(最大1000字符)",
|
||||
"PERMISSION_DENIED": "您没有权限发送消息",
|
||||
"SESSION_EXPIRED": "会话已过期,请重新连接",
|
||||
"ZULIP_ERROR": "消息服务暂时不可用",
|
||||
"INTERNAL_ERROR": "服务器内部错误"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 成员变量
|
||||
# ============================================================================
|
||||
|
||||
# WebSocket 管理器
|
||||
var _websocket_manager: WebSocketManager
|
||||
|
||||
# 是否已登录
|
||||
var _is_logged_in: bool = false
|
||||
|
||||
# 消息历史记录(当前会话,最多100条,超过后删除最旧的)
|
||||
var _message_history: Array[Dictionary] = []
|
||||
|
||||
# 历史消息加载状态
|
||||
var _history_loading: bool = false
|
||||
var _has_more_history: bool = true
|
||||
var _oldest_message_timestamp: float = 0.0
|
||||
|
||||
# 消息发送时间戳(用于频率限制)
|
||||
var _message_timestamps: Array[float] = []
|
||||
|
||||
# 当前用户信息
|
||||
var _current_username: String = ""
|
||||
var _current_map: String = ""
|
||||
|
||||
# 游戏 token
|
||||
var _game_token: String = ""
|
||||
|
||||
# ============================================================================
|
||||
# 生命周期方法
|
||||
# ============================================================================
|
||||
|
||||
# 初始化
|
||||
func _ready() -> void:
|
||||
print("ChatManager 初始化完成")
|
||||
|
||||
# 创建 WebSocket 管理器
|
||||
_websocket_manager = WebSocketManager.new()
|
||||
add_child(_websocket_manager)
|
||||
|
||||
# 连接信号
|
||||
_connect_signals()
|
||||
|
||||
# 清理
|
||||
func _exit_tree() -> void:
|
||||
if is_instance_valid(_websocket_manager):
|
||||
_websocket_manager.queue_free()
|
||||
|
||||
# ============================================================================
|
||||
# 公共 API - Token 管理
|
||||
# ============================================================================
|
||||
|
||||
# 设置游戏 token
|
||||
#
|
||||
# 参数:
|
||||
# token: String - 游戏认证 token
|
||||
#
|
||||
# 使用示例:
|
||||
# ChatManager.set_game_token("your_game_token")
|
||||
func set_game_token(token: String) -> void:
|
||||
_game_token = token
|
||||
print("ChatManager: 游戏 token 已设置")
|
||||
|
||||
# 获取游戏 token
|
||||
#
|
||||
# 返回值:
|
||||
# String - 当前游戏 token
|
||||
func get_game_token() -> String:
|
||||
return _game_token
|
||||
|
||||
# ============================================================================
|
||||
# 公共 API - 连接管理
|
||||
# ============================================================================
|
||||
|
||||
# 连接到聊天服务器
|
||||
func connect_to_chat_server() -> void:
|
||||
if _websocket_manager.is_websocket_connected():
|
||||
push_warning("聊天服务器已连接")
|
||||
return
|
||||
|
||||
print("=== ChatManager 开始连接 ===")
|
||||
_websocket_manager.connect_to_game_server()
|
||||
|
||||
# 断开聊天服务器
|
||||
func disconnect_from_chat_server() -> void:
|
||||
print("=== ChatManager 断开连接 ===")
|
||||
|
||||
# 发送登出消息
|
||||
if _is_logged_in:
|
||||
var logout_data := {"type": "logout"}
|
||||
_websocket_manager.send_message(JSON.stringify(logout_data))
|
||||
_is_logged_in = false
|
||||
|
||||
# 断开连接
|
||||
_websocket_manager.disconnect_websocket()
|
||||
|
||||
# 检查是否已连接
|
||||
#
|
||||
# 返回值:
|
||||
# bool - 是否已连接
|
||||
func is_chat_connected() -> bool:
|
||||
return _websocket_manager.is_websocket_connected()
|
||||
|
||||
# ============================================================================
|
||||
# 公共 API - 聊天操作
|
||||
# ============================================================================
|
||||
|
||||
# 发送聊天消息
|
||||
#
|
||||
# 参数:
|
||||
# content: String - 消息内容
|
||||
# scope: String - 消息范围("local" 或具体 topic 名称)
|
||||
#
|
||||
# 使用示例:
|
||||
# ChatManager.send_chat_message("Hello, world!", "local")
|
||||
func send_chat_message(content: String, scope: String = "local") -> void:
|
||||
# 检查连接状态
|
||||
if not _websocket_manager.is_websocket_connected():
|
||||
_handle_error("NOT_CONNECTED", "未连接到聊天服务器")
|
||||
return
|
||||
|
||||
# 检查登录状态
|
||||
if not _is_logged_in:
|
||||
_handle_error("NOT_LOGGED_IN", "尚未登录聊天服务器")
|
||||
return
|
||||
|
||||
# 检查消息长度
|
||||
if content.length() > MAX_MESSAGE_LENGTH:
|
||||
_handle_error("CONTENT_TOO_LONG", "消息内容过长")
|
||||
return
|
||||
|
||||
# 检查频率限制
|
||||
if not can_send_message():
|
||||
var wait_time := get_time_until_next_message()
|
||||
_handle_error("RATE_LIMIT", "请等待 %.1f 秒后再试" % wait_time)
|
||||
return
|
||||
|
||||
# 构建消息数据
|
||||
var message_data := {
|
||||
"type": "chat",
|
||||
"content": content,
|
||||
"scope": scope
|
||||
}
|
||||
|
||||
# 发送消息(JSON 字符串)
|
||||
var json_string := JSON.stringify(message_data)
|
||||
_websocket_manager.send_message(json_string)
|
||||
|
||||
# 记录发送时间
|
||||
_record_message_timestamp()
|
||||
|
||||
# 添加到历史
|
||||
_add_message_to_history({
|
||||
"from_user": _current_username,
|
||||
"content": content,
|
||||
"timestamp": Time.get_unix_time_from_system(),
|
||||
"is_self": true
|
||||
})
|
||||
|
||||
print("📤 发送聊天消息: ", content)
|
||||
|
||||
# 消息发送完成回调
|
||||
func _on_chat_message_sent(request_id: String, success: bool, data: Dictionary, error_info: Dictionary) -> void:
|
||||
if success:
|
||||
print("✅ 消息发送成功: ", data)
|
||||
var message_id: String = str(data.get("data", {}).get("id", ""))
|
||||
var timestamp: float = Time.get_unix_time_from_system()
|
||||
chat_message_sent.emit(message_id, timestamp)
|
||||
EventSystem.emit_event(EventNames.CHAT_MESSAGE_SENT, {
|
||||
"message_id": message_id,
|
||||
"timestamp": timestamp
|
||||
})
|
||||
else:
|
||||
print("❌ 消息发送失败: ", error_info)
|
||||
_handle_error("SEND_FAILED", error_info.get("message", "发送失败"))
|
||||
|
||||
# 更新玩家位置
|
||||
#
|
||||
# 参数:
|
||||
# x: float - X 坐标
|
||||
# y: float - Y 坐标
|
||||
# map_id: String - 地图 ID
|
||||
#
|
||||
# 使用示例:
|
||||
# ChatManager.update_player_position(150.0, 200.0, "novice_village")
|
||||
func update_player_position(x: float, y: float, map_id: String) -> void:
|
||||
if not _websocket_manager.is_websocket_connected():
|
||||
return
|
||||
|
||||
var position_data := {
|
||||
"type": "position",
|
||||
"x": x,
|
||||
"y": y,
|
||||
"mapId": map_id
|
||||
}
|
||||
|
||||
# 发送消息(JSON 字符串)
|
||||
var json_string := JSON.stringify(position_data)
|
||||
_websocket_manager.send_message(json_string)
|
||||
|
||||
print("📍 更新位置: (%.2f, %.2f) in %s" % [x, y, map_id])
|
||||
|
||||
# ============================================================================
|
||||
# 公共 API - 频率限制
|
||||
# ============================================================================
|
||||
|
||||
# 检查是否可以发送消息
|
||||
#
|
||||
# 返回值:
|
||||
# bool - 是否可以发送
|
||||
func can_send_message() -> bool:
|
||||
var current_time := Time.get_unix_time_from_system()
|
||||
|
||||
# 清理过期的时间戳
|
||||
var filter_func := func(timestamp: float) -> bool:
|
||||
return current_time - timestamp < RATE_LIMIT_WINDOW
|
||||
_message_timestamps = _message_timestamps.filter(filter_func)
|
||||
|
||||
# 检查数量
|
||||
return _message_timestamps.size() < RATE_LIMIT_MESSAGES
|
||||
|
||||
# 获取距离下次可发送消息的时间
|
||||
#
|
||||
# 返回值:
|
||||
# float - 等待时间(秒)
|
||||
func get_time_until_next_message() -> float:
|
||||
if _message_timestamps.is_empty():
|
||||
return 0.0
|
||||
|
||||
if _message_timestamps.size() < RATE_LIMIT_MESSAGES:
|
||||
return 0.0
|
||||
|
||||
# 找到最早的时间戳
|
||||
var earliest_timestamp: float = _message_timestamps[0]
|
||||
var current_time := Time.get_unix_time_from_system()
|
||||
var elapsed := current_time - earliest_timestamp
|
||||
|
||||
if elapsed >= RATE_LIMIT_WINDOW:
|
||||
return 0.0
|
||||
|
||||
return RATE_LIMIT_WINDOW - elapsed
|
||||
|
||||
# ============================================================================
|
||||
# 公共 API - 消息历史
|
||||
# ============================================================================
|
||||
|
||||
# 获取消息历史
|
||||
#
|
||||
# 返回值:
|
||||
# Array[Dictionary] - 消息历史数组
|
||||
func get_message_history() -> Array[Dictionary]:
|
||||
return _message_history.duplicate()
|
||||
|
||||
# 清空消息历史
|
||||
func clear_message_history() -> void:
|
||||
_message_history.clear()
|
||||
print("🧹 清空消息历史")
|
||||
|
||||
# 重置当前会话(每次登录/重连时调用)
|
||||
#
|
||||
# 功能:
|
||||
# - 清空当前会话消息缓存
|
||||
# - 重置历史消息加载状态
|
||||
# - 不影响 Zulip 后端的历史消息
|
||||
#
|
||||
# 使用场景:
|
||||
# - 用户登录成功后
|
||||
# - 重新连接到聊天服务器后
|
||||
func reset_session() -> void:
|
||||
_message_history.clear()
|
||||
_history_loading = false
|
||||
_has_more_history = true
|
||||
_oldest_message_timestamp = 0.0
|
||||
print("🔄 重置聊天会话")
|
||||
|
||||
# 加载历史消息(按需从 Zulip 后端获取)
|
||||
#
|
||||
# 参数:
|
||||
# count: int - 要加载的消息数量(默认 HISTORY_PAGE_SIZE)
|
||||
#
|
||||
# 功能:
|
||||
# - 从 Zulip 后端获取历史消息
|
||||
# - 添加到当前会话历史开头
|
||||
# - 触发 CHAT_MESSAGE_RECEIVED 事件显示消息
|
||||
#
|
||||
# 使用场景:
|
||||
# - 用户滚动到聊天窗口顶部
|
||||
# - 用户主动点击"加载历史"按钮
|
||||
#
|
||||
# 注意:
|
||||
# - 这是异步操作,需要通过 Zulip API 实现
|
||||
# - 当前实现为占位符,需要后端 API 支持
|
||||
func load_history(count: int = HISTORY_PAGE_SIZE) -> void:
|
||||
if _history_loading:
|
||||
print("⏳ 历史消息正在加载中...")
|
||||
return
|
||||
|
||||
if not _has_more_history:
|
||||
print("📚 没有更多历史消息")
|
||||
return
|
||||
|
||||
_history_loading = true
|
||||
print("📜 开始加载历史消息,数量: ", count)
|
||||
|
||||
# TODO: 实现从 Zulip 后端获取历史消息
|
||||
# NetworkManager.get_chat_history(_oldest_message_timestamp, count, _on_history_loaded)
|
||||
|
||||
# 临时实现:模拟历史消息加载(测试用)
|
||||
# await get_tree().create_timer(1.0).timeout
|
||||
# _on_history_loaded([])
|
||||
|
||||
# 历史消息加载完成回调
|
||||
func _on_history_loaded(messages: Array) -> void:
|
||||
_history_loading = false
|
||||
|
||||
if messages.is_empty():
|
||||
_has_more_history = false
|
||||
print("📚 没有更多历史消息")
|
||||
return
|
||||
|
||||
print("📜 历史消息加载完成,数量: ", messages.size())
|
||||
|
||||
# 将历史消息插入到当前会话历史开头
|
||||
for i in range(messages.size() - 1, -1, -1):
|
||||
var message: Dictionary = messages[i]
|
||||
_message_history.push_front(message)
|
||||
|
||||
# 触发事件显示消息(Signal Up)
|
||||
EventSystem.emit_event(EventNames.CHAT_MESSAGE_RECEIVED, {
|
||||
"from_user": message.get("from_user", ""),
|
||||
"content": message.get("content", ""),
|
||||
"show_bubble": false,
|
||||
"timestamp": message.get("timestamp", 0.0),
|
||||
"is_history": true # 标记为历史消息
|
||||
})
|
||||
|
||||
# 更新最旧消息时间戳
|
||||
var oldest: Dictionary = messages.back()
|
||||
if oldest.has("timestamp"):
|
||||
_oldest_message_timestamp = oldest.timestamp
|
||||
|
||||
# 检查是否还有更多历史
|
||||
if messages.size() < HISTORY_PAGE_SIZE:
|
||||
_has_more_history = false
|
||||
|
||||
# ============================================================================
|
||||
# 内部方法 - 信号连接
|
||||
# ============================================================================
|
||||
|
||||
# 连接信号
|
||||
func _connect_signals() -> void:
|
||||
# WebSocket 管理器信号
|
||||
_websocket_manager.connection_state_changed.connect(_on_connection_state_changed)
|
||||
_websocket_manager.data_received.connect(_on_data_received)
|
||||
|
||||
# 发送登录消息
|
||||
func _send_login_message() -> void:
|
||||
print("📤 发送登录消息...")
|
||||
|
||||
var login_data := {
|
||||
"type": "login",
|
||||
"token": _game_token
|
||||
}
|
||||
|
||||
var json_string := JSON.stringify(login_data)
|
||||
_websocket_manager.send_message(json_string)
|
||||
|
||||
print(" Token: ", _game_token.left(20) + "..." if _game_token.length() > 20 else _game_token)
|
||||
|
||||
# 连接状态变化
|
||||
func _on_connection_state_changed(state: int) -> void:
|
||||
var state_names := ["DISCONNECTED", "CONNECTING", "CONNECTED", "RECONNECTING", "ERROR"]
|
||||
print("📡 ChatManager: 连接状态变化 - ", state_names[state])
|
||||
|
||||
# 发射信号
|
||||
chat_connection_state_changed.emit(state)
|
||||
|
||||
# 通过 EventSystem 广播(Signal Up)
|
||||
EventSystem.emit_event(EventNames.CHAT_CONNECTION_STATE_CHANGED, {
|
||||
"state": state
|
||||
})
|
||||
|
||||
# 如果连接成功,发送登录消息
|
||||
if state == 2: # CONNECTED
|
||||
_send_login_message()
|
||||
|
||||
# ============================================================================
|
||||
# 内部方法 - 消息处理
|
||||
# ============================================================================
|
||||
|
||||
# WebSocket 数据接收
|
||||
func _on_data_received(message: String) -> void:
|
||||
# 解析 JSON 消息
|
||||
var json := JSON.new()
|
||||
var parse_result := json.parse(message)
|
||||
|
||||
if parse_result != OK:
|
||||
print("❌ ChatManager: JSON 解析失败 - ", message)
|
||||
return
|
||||
|
||||
var data: Dictionary = json.data
|
||||
|
||||
# 检查消息类型字段
|
||||
var message_type: String = data.get("t", "")
|
||||
|
||||
match message_type:
|
||||
"login_success":
|
||||
_handle_login_success(data)
|
||||
"login_error":
|
||||
_handle_login_error(data)
|
||||
"chat_sent":
|
||||
_handle_chat_sent(data)
|
||||
"chat_error":
|
||||
_handle_chat_error(data)
|
||||
"chat_render":
|
||||
_handle_chat_render(data)
|
||||
"position_updated":
|
||||
_handle_position_updated(data)
|
||||
_:
|
||||
print("⚠️ ChatManager: 未处理的消息类型 - ", message_type)
|
||||
print(" 消息内容: ", data)
|
||||
|
||||
# 处理登录成功
|
||||
func _handle_login_success(data: Dictionary) -> void:
|
||||
print("✅ ChatManager: 登录成功")
|
||||
|
||||
_is_logged_in = true
|
||||
_current_username = data.get("username", "")
|
||||
_current_map = data.get("currentMap", "")
|
||||
|
||||
# 重置当前会话缓存(每次登录/重连都清空,重新开始接收消息)
|
||||
reset_session()
|
||||
|
||||
print(" 用户名: ", _current_username)
|
||||
print(" 地图: ", _current_map)
|
||||
|
||||
# 通过 EventSystem 广播(Signal Up)
|
||||
EventSystem.emit_event(EventNames.CHAT_LOGIN_SUCCESS, {
|
||||
"username": _current_username,
|
||||
"current_map": _current_map
|
||||
})
|
||||
|
||||
# 处理登录失败
|
||||
func _handle_login_error(data: Dictionary) -> void:
|
||||
var error_message: String = data.get("message", "登录失败")
|
||||
|
||||
print("❌ ChatManager: 登录失败 - ", error_message)
|
||||
|
||||
_is_logged_in = false
|
||||
|
||||
# 通过 EventSystem 广播错误(Signal Up)
|
||||
EventSystem.emit_event(EventNames.CHAT_LOGIN_FAILED, {
|
||||
"error_code": "LOGIN_FAILED",
|
||||
"message": error_message
|
||||
})
|
||||
|
||||
# 处理聊天消息发送成功
|
||||
func _handle_chat_sent(data: Dictionary) -> void:
|
||||
var message_id: String = str(data.get("messageId", ""))
|
||||
var timestamp: float = data.get("timestamp", 0.0)
|
||||
|
||||
print("✅ 消息发送成功: ", message_id)
|
||||
|
||||
# 发射信号
|
||||
chat_message_sent.emit(message_id, timestamp)
|
||||
|
||||
# 通过 EventSystem 广播(Signal Up)
|
||||
EventSystem.emit_event(EventNames.CHAT_MESSAGE_SENT, {
|
||||
"message_id": message_id,
|
||||
"timestamp": timestamp
|
||||
})
|
||||
|
||||
# 处理聊天消息发送失败
|
||||
func _handle_chat_error(data: Dictionary) -> void:
|
||||
var error_message: String = data.get("message", "消息发送失败")
|
||||
|
||||
print("❌ ChatManager: 聊天错误 - ", error_message)
|
||||
|
||||
# 通过 EventSystem 广播错误(Signal Up)
|
||||
EventSystem.emit_event(EventNames.CHAT_ERROR_OCCURRED, {
|
||||
"error_code": "CHAT_SEND_FAILED",
|
||||
"message": error_message
|
||||
})
|
||||
|
||||
# 处理接收到的聊天消息
|
||||
func _handle_chat_render(data: Dictionary) -> void:
|
||||
var from_user: String = data.get("from", "")
|
||||
var content: String = data.get("txt", "")
|
||||
var show_bubble: bool = data.get("bubble", false)
|
||||
var timestamp: float = data.get("timestamp", 0.0)
|
||||
|
||||
print("📨 收到聊天消息: ", from_user, " -> ", content)
|
||||
|
||||
# 添加到历史
|
||||
_add_message_to_history({
|
||||
"from_user": from_user,
|
||||
"content": content,
|
||||
"timestamp": timestamp,
|
||||
"is_self": false
|
||||
})
|
||||
|
||||
# 发射信号
|
||||
chat_message_received.emit(from_user, content, show_bubble, timestamp)
|
||||
|
||||
# 通过 EventSystem 广播(Signal Up)
|
||||
EventSystem.emit_event(EventNames.CHAT_MESSAGE_RECEIVED, {
|
||||
"from_user": from_user,
|
||||
"content": content,
|
||||
"show_bubble": show_bubble,
|
||||
"timestamp": timestamp
|
||||
})
|
||||
|
||||
# 处理位置更新成功
|
||||
func _handle_position_updated(data: Dictionary) -> void:
|
||||
var stream: String = data.get("stream", "")
|
||||
var topic: String = data.get("topic", "")
|
||||
|
||||
print("✅ 位置更新成功: ", stream, " / ", topic)
|
||||
|
||||
# 发射信号
|
||||
chat_position_updated.emit(stream, topic)
|
||||
|
||||
# 通过 EventSystem 广播(Signal Up)
|
||||
EventSystem.emit_event(EventNames.CHAT_POSITION_UPDATED, {
|
||||
"stream": stream,
|
||||
"topic": topic
|
||||
})
|
||||
|
||||
# 处理错误响应(如果需要)
|
||||
func _handle_error_response(data: Dictionary) -> void:
|
||||
var error_code: String = data.get("code", "")
|
||||
var error_message: String = data.get("message", "")
|
||||
|
||||
_handle_error(error_code, error_message)
|
||||
|
||||
# 处理 Socket 错误(如果需要)
|
||||
func _on_socket_error(error: String) -> void:
|
||||
_handle_error("SOCKET_ERROR", error)
|
||||
|
||||
# ============================================================================
|
||||
# 内部方法 - 工具函数
|
||||
# ============================================================================
|
||||
|
||||
# 处理错误
|
||||
func _handle_error(error_code: String, error_message: String) -> void:
|
||||
print("❌ ChatManager 错误: [", error_code, "] ", error_message)
|
||||
|
||||
# 获取用户友好的错误消息
|
||||
var user_message: String = CHAT_ERROR_MESSAGES.get(error_code, error_message) as String
|
||||
|
||||
# 发射信号
|
||||
chat_error_occurred.emit(error_code, user_message)
|
||||
|
||||
# 通过 EventSystem 广播(Signal Up)
|
||||
EventSystem.emit_event(EventNames.CHAT_ERROR_OCCURRED, {
|
||||
"error_code": error_code,
|
||||
"message": user_message
|
||||
})
|
||||
|
||||
# 特殊处理认证失败
|
||||
if error_code == "AUTH_FAILED" or error_code == "SESSION_EXPIRED":
|
||||
_is_logged_in = false
|
||||
EventSystem.emit_event(EventNames.CHAT_LOGIN_FAILED, {
|
||||
"error_code": error_code
|
||||
})
|
||||
|
||||
# 记录消息发送时间戳
|
||||
func _record_message_timestamp() -> void:
|
||||
var current_time := Time.get_unix_time_from_system()
|
||||
_message_timestamps.append(current_time)
|
||||
|
||||
# 添加消息到当前会话历史
|
||||
func _add_message_to_history(message: Dictionary) -> void:
|
||||
_message_history.append(message)
|
||||
|
||||
# 更新最旧消息时间戳(用于历史消息加载)
|
||||
if _oldest_message_timestamp == 0.0 or message.timestamp < _oldest_message_timestamp:
|
||||
_oldest_message_timestamp = message.timestamp
|
||||
|
||||
# 限制当前会话消息数量(超过后删除最旧的)
|
||||
if _message_history.size() > MAX_SESSION_MESSAGES:
|
||||
_message_history.pop_front()
|
||||
@@ -1 +0,0 @@
|
||||
uid://b6lnbss2i3pss
|
||||
@@ -1,461 +0,0 @@
|
||||
extends Node
|
||||
|
||||
# ============================================================================
|
||||
# WebSocketManager.gd - WebSocket 连接生命周期管理(原生 WebSocket 版本)
|
||||
# ============================================================================
|
||||
# 管理 WebSocket 连接状态、自动重连和错误恢复
|
||||
#
|
||||
# 核心职责:
|
||||
# - 连接状态管理(断开、连接中、已连接、重连中)
|
||||
# - 自动重连机制(指数退避)
|
||||
# - 连接错误恢复
|
||||
# - WebSocket 消息发送/接收
|
||||
# ============================================================================
|
||||
# 使用方式:
|
||||
# WebSocketManager.connect_to_game_server()
|
||||
# WebSocketManager.connection_state_changed.connect(_on_state_changed)
|
||||
#
|
||||
# 注意事项:
|
||||
# - 作为自动加载单例,全局可访问
|
||||
# - 自动处理连接断开和重连
|
||||
# - 通过信号通知连接状态变化
|
||||
# ============================================================================
|
||||
|
||||
class_name WebSocketManager
|
||||
|
||||
# ============================================================================
|
||||
# 信号定义
|
||||
# ============================================================================
|
||||
|
||||
# 连接状态变化信号
|
||||
# 参数:
|
||||
# new_state: ConnectionState - 新的连接状态
|
||||
signal connection_state_changed(new_state: ConnectionState)
|
||||
|
||||
# 连接丢失信号
|
||||
signal connection_lost()
|
||||
|
||||
# 重连成功信号
|
||||
signal reconnection_succeeded()
|
||||
|
||||
# 重连失败信号
|
||||
# 参数:
|
||||
# attempt: int - 当前重连尝试次数
|
||||
# max_attempts: int - 最大重连次数
|
||||
signal reconnection_failed(attempt: int, max_attempts: int)
|
||||
|
||||
# WebSocket 消息接收信号
|
||||
# 参数:
|
||||
# message: String - 接收到的消息内容(JSON 字符串)
|
||||
signal data_received(message: String)
|
||||
|
||||
# ============================================================================
|
||||
# 枚举定义
|
||||
# ============================================================================
|
||||
|
||||
# 连接状态枚举
|
||||
enum ConnectionState {
|
||||
DISCONNECTED, # 未连接
|
||||
CONNECTING, # 连接中
|
||||
CONNECTED, # 已连接
|
||||
RECONNECTING, # 重连中
|
||||
ERROR # 错误状态
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 常量定义
|
||||
# ============================================================================
|
||||
|
||||
# WebSocket 服务器 URL(原生 WebSocket)
|
||||
const WEBSOCKET_URL: String = "wss://whaletownend.xinghangee.icu/game"
|
||||
|
||||
# 默认最大重连次数
|
||||
const DEFAULT_MAX_RECONNECT_ATTEMPTS: int = 5
|
||||
|
||||
# 默认重连基础延迟(秒)
|
||||
const DEFAULT_RECONNECT_BASE_DELAY: float = 3.0
|
||||
|
||||
# 最大重连延迟(秒)
|
||||
const MAX_RECONNECT_DELAY: float = 30.0
|
||||
|
||||
# ============================================================================
|
||||
# 成员变量
|
||||
# ============================================================================
|
||||
|
||||
# WebSocket peer
|
||||
var _websocket_peer: WebSocketPeer = WebSocketPeer.new()
|
||||
|
||||
# 当前连接状态
|
||||
var _connection_state: ConnectionState = ConnectionState.DISCONNECTED
|
||||
|
||||
# 自动重连启用标志
|
||||
var _auto_reconnect_enabled: bool = true
|
||||
|
||||
# 最大重连次数
|
||||
var _max_reconnect_attempts: int = DEFAULT_MAX_RECONNECT_ATTEMPTS
|
||||
|
||||
# 重连基础延迟
|
||||
var _reconnect_base_delay: float = DEFAULT_RECONNECT_BASE_DELAY
|
||||
|
||||
# 当前重连尝试次数
|
||||
var _reconnect_attempt: int = 0
|
||||
|
||||
# 重连定时器
|
||||
var _reconnect_timer: Timer = Timer.new()
|
||||
|
||||
# 是否为正常关闭(非异常断开)
|
||||
var _clean_close: bool = true
|
||||
|
||||
# 心跳定时器
|
||||
var _heartbeat_timer: Timer = Timer.new()
|
||||
|
||||
# 心跳间隔(秒)
|
||||
const HEARTBEAT_INTERVAL: float = 30.0
|
||||
|
||||
# ============================================================================
|
||||
# 生命周期方法
|
||||
# ============================================================================
|
||||
|
||||
# 初始化
|
||||
func _ready() -> void:
|
||||
print("WebSocketManager 初始化完成")
|
||||
|
||||
# 设置重连定时器
|
||||
_setup_reconnect_timer()
|
||||
|
||||
# 设置心跳定时器
|
||||
_setup_heartbeat_timer()
|
||||
|
||||
# 启动处理循环
|
||||
set_process(true)
|
||||
|
||||
# 处理每帧
|
||||
func _process(_delta: float) -> void:
|
||||
# 检查 WebSocket 状态变化
|
||||
_check_websocket_state()
|
||||
|
||||
var state: WebSocketPeer.State = _websocket_peer.get_ready_state()
|
||||
|
||||
# 调试:打印状态变化
|
||||
if _connection_state == ConnectionState.CONNECTING:
|
||||
var peer_state_name = ["DISCONNECTED", "CONNECTING", "OPEN", "CLOSING", "CLOSED"][state]
|
||||
print("📡 WebSocket 状态: peer=%s, manager=%s" % [peer_state_name, ConnectionState.keys()[_connection_state]])
|
||||
|
||||
if state == WebSocketPeer.STATE_OPEN:
|
||||
# 接收数据
|
||||
_websocket_peer.poll()
|
||||
|
||||
# 处理收到的数据
|
||||
while _websocket_peer.get_available_packet_count() > 0:
|
||||
var packet: PackedByteArray = _websocket_peer.get_packet()
|
||||
var message: String = packet.get_string_from_utf8()
|
||||
|
||||
# 发射消息接收信号
|
||||
data_received.emit(message)
|
||||
|
||||
# 打印调试信息
|
||||
print("📨 WebSocket 收到消息: ", message)
|
||||
|
||||
# 清理
|
||||
func _exit_tree() -> void:
|
||||
_disconnect()
|
||||
|
||||
if is_instance_valid(_reconnect_timer):
|
||||
_reconnect_timer.stop()
|
||||
_reconnect_timer.queue_free()
|
||||
|
||||
if is_instance_valid(_heartbeat_timer):
|
||||
_heartbeat_timer.stop()
|
||||
_heartbeat_timer.queue_free()
|
||||
|
||||
# ============================================================================
|
||||
# 公共 API - 连接管理
|
||||
# ============================================================================
|
||||
|
||||
# 连接到游戏服务器
|
||||
func connect_to_game_server() -> void:
|
||||
if _connection_state == ConnectionState.CONNECTED or _connection_state == ConnectionState.CONNECTING:
|
||||
push_warning("已经在连接或已连接状态")
|
||||
return
|
||||
|
||||
print("=== WebSocketManager 开始连接 ===")
|
||||
print("服务器 URL: ", WEBSOCKET_URL)
|
||||
print("WebSocket 连接中...")
|
||||
|
||||
_set_connection_state(ConnectionState.CONNECTING)
|
||||
_clean_close = true
|
||||
_reconnect_attempt = 0
|
||||
|
||||
var err: Error = _websocket_peer.connect_to_url(WEBSOCKET_URL)
|
||||
if err != OK:
|
||||
print("❌ WebSocket 连接失败: ", error_string(err))
|
||||
_set_connection_state(ConnectionState.ERROR)
|
||||
return
|
||||
|
||||
# 启动心跳
|
||||
_start_heartbeat()
|
||||
|
||||
# 断开 WebSocket 连接
|
||||
func disconnect_websocket() -> void:
|
||||
print("=== WebSocketManager 断开连接 ===")
|
||||
_disconnect()
|
||||
|
||||
# 断开连接(内部方法)
|
||||
func _disconnect() -> void:
|
||||
_clean_close = true
|
||||
|
||||
# 停止重连定时器
|
||||
_reconnect_timer.stop()
|
||||
|
||||
# 停止心跳
|
||||
_heartbeat_timer.stop()
|
||||
|
||||
# 关闭 WebSocket
|
||||
if _websocket_peer.get_ready_state() == WebSocketPeer.STATE_OPEN:
|
||||
_websocket_peer.close()
|
||||
|
||||
_set_connection_state(ConnectionState.DISCONNECTED)
|
||||
|
||||
# 检查 WebSocket 是否已连接
|
||||
#
|
||||
# 返回值:
|
||||
# bool - WebSocket 是否已连接
|
||||
func is_websocket_connected() -> bool:
|
||||
return _connection_state == ConnectionState.CONNECTED
|
||||
|
||||
# 获取当前连接状态
|
||||
#
|
||||
# 返回值:
|
||||
# ConnectionState - 当前连接状态
|
||||
func get_connection_state() -> ConnectionState:
|
||||
return _connection_state
|
||||
|
||||
# ============================================================================
|
||||
# 公共 API - 消息发送
|
||||
# ============================================================================
|
||||
|
||||
# 发送 WebSocket 消息
|
||||
#
|
||||
# 参数:
|
||||
# message: String - 要发送的消息内容(JSON 字符串)
|
||||
#
|
||||
# 返回值:
|
||||
# Error - 错误码,OK 表示成功
|
||||
func send_message(message: String) -> Error:
|
||||
if _websocket_peer.get_ready_state() != WebSocketPeer.STATE_OPEN:
|
||||
print("❌ WebSocket 未连接,无法发送消息")
|
||||
return ERR_UNCONFIGURED
|
||||
|
||||
var err: Error = _websocket_peer.send_text(message)
|
||||
if err != OK:
|
||||
print("❌ WebSocket 发送消息失败: ", error_string(err))
|
||||
return err
|
||||
|
||||
print("📤 发送 WebSocket 消息: ", message)
|
||||
return OK
|
||||
|
||||
# ============================================================================
|
||||
# 公共 API - 自动重连
|
||||
# ============================================================================
|
||||
|
||||
# 启用/禁用自动重连
|
||||
#
|
||||
# 参数:
|
||||
# enabled: bool - 是否启用自动重连
|
||||
# max_attempts: int - 最大重连次数(默认 5)
|
||||
# base_delay: float - 基础重连延迟,秒(默认 3.0)
|
||||
#
|
||||
# 使用示例:
|
||||
# WebSocketManager.enable_auto_reconnect(true, 5, 3.0)
|
||||
func enable_auto_reconnect(enabled: bool, max_attempts: int = DEFAULT_MAX_RECONNECT_ATTEMPTS, base_delay: float = DEFAULT_RECONNECT_BASE_DELAY) -> void:
|
||||
_auto_reconnect_enabled = enabled
|
||||
_max_reconnect_attempts = max_attempts
|
||||
_reconnect_base_delay = base_delay
|
||||
|
||||
print("自动重连: ", "启用" if enabled else "禁用")
|
||||
print("最大重连次数: ", _max_reconnect_attempts)
|
||||
print("基础重连延迟: ", _reconnect_base_delay, " 秒")
|
||||
|
||||
# 获取重连信息
|
||||
#
|
||||
# 返回值:
|
||||
# Dictionary - 重连信息 {enabled, attempt, max_attempts, delay}
|
||||
func get_reconnect_info() -> Dictionary:
|
||||
return {
|
||||
"enabled": _auto_reconnect_enabled,
|
||||
"attempt": _reconnect_attempt,
|
||||
"max_attempts": _max_reconnect_attempts,
|
||||
"next_delay": _calculate_reconnect_delay() if _connection_state == ConnectionState.RECONNECTING else 0.0
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 内部方法 - 连接状态管理
|
||||
# ============================================================================
|
||||
|
||||
# 设置连接状态
|
||||
func _set_connection_state(new_state: ConnectionState) -> void:
|
||||
if _connection_state == new_state:
|
||||
return
|
||||
|
||||
_connection_state = new_state
|
||||
print("📡 连接状态变更: ", ConnectionState.keys()[new_state])
|
||||
|
||||
# 发射信号
|
||||
connection_state_changed.emit(new_state)
|
||||
|
||||
# ============================================================================
|
||||
# 内部方法 - WebSocket 状态监控
|
||||
# ============================================================================
|
||||
|
||||
# 检查 WebSocket 状态变化
|
||||
func _check_websocket_state() -> void:
|
||||
# 必须先 poll 才能获取最新状态
|
||||
_websocket_peer.poll()
|
||||
|
||||
var state: WebSocketPeer.State = _websocket_peer.get_ready_state()
|
||||
|
||||
match state:
|
||||
WebSocketPeer.STATE_CONNECTING:
|
||||
# 正在连接
|
||||
if _connection_state != ConnectionState.CONNECTING and _connection_state != ConnectionState.RECONNECTING:
|
||||
_set_connection_state(ConnectionState.CONNECTING)
|
||||
|
||||
WebSocketPeer.STATE_OPEN:
|
||||
# 连接成功
|
||||
if _connection_state != ConnectionState.CONNECTED:
|
||||
_on_websocket_connected()
|
||||
|
||||
WebSocketPeer.STATE_CLOSING:
|
||||
# 正在关闭
|
||||
pass
|
||||
|
||||
WebSocketPeer.STATE_CLOSED:
|
||||
# 连接关闭
|
||||
var code: int = _websocket_peer.get_close_code()
|
||||
var reason: String = _websocket_peer.get_close_reason()
|
||||
print("🔌 WebSocket 关闭: code=%d, reason=%s" % [code, reason])
|
||||
_on_websocket_closed(code != 0) # code=0 表示正常关闭
|
||||
|
||||
# WebSocket 连接成功处理
|
||||
func _on_websocket_connected() -> void:
|
||||
print("✅ WebSocketManager: WebSocket 连接成功")
|
||||
|
||||
# 如果是重连,发射重连成功信号
|
||||
if _connection_state == ConnectionState.RECONNECTING:
|
||||
_reconnect_attempt = 0
|
||||
reconnection_succeeded.emit()
|
||||
print("🔄 重连成功")
|
||||
|
||||
_set_connection_state(ConnectionState.CONNECTED)
|
||||
|
||||
# WebSocket 连接关闭处理
|
||||
func _on_websocket_closed(clean_close: bool) -> void:
|
||||
print("🔌 WebSocketManager: WebSocket 连接断开")
|
||||
print(" 正常关闭: ", clean_close)
|
||||
|
||||
_clean_close = clean_close
|
||||
|
||||
# 如果是异常断开且启用了自动重连
|
||||
if not clean_close and _auto_reconnect_enabled:
|
||||
connection_lost.emit()
|
||||
_attempt_reconnect()
|
||||
else:
|
||||
_set_connection_state(ConnectionState.DISCONNECTED)
|
||||
|
||||
# ============================================================================
|
||||
# 内部方法 - 重连机制
|
||||
# ============================================================================
|
||||
|
||||
# 设置重连定时器
|
||||
func _setup_reconnect_timer() -> void:
|
||||
_reconnect_timer = Timer.new()
|
||||
_reconnect_timer.one_shot = true
|
||||
_reconnect_timer.autostart = false
|
||||
add_child(_reconnect_timer)
|
||||
|
||||
_reconnect_timer.timeout.connect(_on_reconnect_timeout)
|
||||
|
||||
# 尝试重连
|
||||
func _attempt_reconnect() -> void:
|
||||
# 检查是否超过最大重连次数
|
||||
if _reconnect_attempt >= _max_reconnect_attempts:
|
||||
print("❌ 达到最大重连次数 (", _max_reconnect_attempts, "),停止重连")
|
||||
reconnection_failed.emit(_reconnect_attempt, _max_reconnect_attempts)
|
||||
_set_connection_state(ConnectionState.ERROR)
|
||||
return
|
||||
|
||||
_reconnect_attempt += 1
|
||||
_set_connection_state(ConnectionState.RECONNECTING)
|
||||
|
||||
# 计算重连延迟(指数退避)
|
||||
var delay: float = _calculate_reconnect_delay()
|
||||
print("🔄 尝试重连 (", _reconnect_attempt, "/", _max_reconnect_attempts, ")")
|
||||
print(" 延迟: ", delay, " 秒")
|
||||
|
||||
# 启动重连定时器
|
||||
_reconnect_timer.start(delay)
|
||||
|
||||
# 计算重连延迟(指数退避)
|
||||
func _calculate_reconnect_delay() -> float:
|
||||
# 指数退避: base_delay * 2^(attempt-1)
|
||||
var delay: float = _reconnect_base_delay * pow(2.0, _reconnect_attempt - 1)
|
||||
|
||||
# 限制最大延迟
|
||||
return min(delay, MAX_RECONNECT_DELAY)
|
||||
|
||||
# 重连定时器超时处理
|
||||
func _on_reconnect_timeout() -> void:
|
||||
print("⏰ 重连定时器超时,开始重连...")
|
||||
_clean_close = false
|
||||
connect_to_game_server()
|
||||
|
||||
# ============================================================================
|
||||
# 内部方法 - 心跳机制
|
||||
# ============================================================================
|
||||
|
||||
# 设置心跳定时器
|
||||
func _setup_heartbeat_timer() -> void:
|
||||
_heartbeat_timer = Timer.new()
|
||||
_heartbeat_timer.wait_time = HEARTBEAT_INTERVAL
|
||||
_heartbeat_timer.one_shot = false
|
||||
_heartbeat_timer.autostart = false
|
||||
add_child(_heartbeat_timer)
|
||||
|
||||
_heartbeat_timer.timeout.connect(_on_heartbeat)
|
||||
|
||||
# 启动心跳
|
||||
func _start_heartbeat() -> void:
|
||||
_heartbeat_timer.start()
|
||||
|
||||
# 停止心跳
|
||||
func _stop_heartbeat() -> void:
|
||||
_heartbeat_timer.stop()
|
||||
|
||||
# 心跳超时处理
|
||||
func _on_heartbeat() -> void:
|
||||
# 不发送心跳,避免服务器返回 "消息格式错误"
|
||||
# 如果需要心跳,服务器应该支持特定格式
|
||||
pass
|
||||
|
||||
# ============================================================================
|
||||
# 工具方法
|
||||
# ============================================================================
|
||||
|
||||
# 获取连接状态描述
|
||||
#
|
||||
# 返回值:
|
||||
# String - 连接状态描述
|
||||
func get_state_description() -> String:
|
||||
match _connection_state:
|
||||
ConnectionState.DISCONNECTED:
|
||||
return "未连接"
|
||||
ConnectionState.CONNECTING:
|
||||
return "连接中"
|
||||
ConnectionState.CONNECTED:
|
||||
return "已连接"
|
||||
ConnectionState.RECONNECTING:
|
||||
return "重连中 (%d/%d)" % [_reconnect_attempt, _max_reconnect_attempts]
|
||||
ConnectionState.ERROR:
|
||||
return "错误"
|
||||
_:
|
||||
return "未知状态"
|
||||
@@ -1 +0,0 @@
|
||||
uid://dmbgtbf6gyk6t
|
||||
Reference in New Issue
Block a user