Initial commit: Client Doc docs Server Tools

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ud18010
2026-07-10 10:24:29 +08:00
co-authored by Cursor
commit 7e35d8da31
3374 changed files with 680813 additions and 0 deletions
@@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using XWorld.Framework;
using XWorld.Framework.Protocol;
namespace XGame.MiniGame
{
// 客户端到服务端的帧通道。每帧轮询底层 socket,解码出的帧分发:
// Game 帧 -> OnGameMessage(NetMessage)(交给 IGameClient.OnNetMessage
// Framework 帧 -> OnFrameworkFrame(Frame)(宿主处理 MatchFound/RoomEnd 等)
public sealed class MiniGameNetChannel
{
private readonly XWebSocket _sock;
private readonly List<byte> _recvBuffer = new List<byte>(4096);
public Action<NetMessage> OnGameMessage;
public Action<Frame> OnFrameworkFrame;
public MiniGameNetChannel(XWebSocket sock) { _sock = sock; }
public bool IsConnected => _sock != null && _sock.IsConected();
// 业务输入:IGameClient 调 ctx.Send(NetMessage) -> 这里包成 Game 帧发出
public void SendGame(NetMessage msg)
{
byte[] frame = FrameCodec.Encode(new Frame(Channel.Game, msg.Opcode, msg.Payload));
SendRaw(frame);
}
public void SendFramework(FrameworkOpcode op, byte[] payload)
{
byte[] frame = FrameCodec.Encode(new Frame(Channel.Framework, (ushort)op, payload));
SendRaw(frame);
}
private void SendRaw(byte[] frame)
{
string err = null;
_sock.send(frame, null, ref err);
if (!string.IsNullOrEmpty(err)) Debug.LogError("[MiniGameNetChannel] send err: " + err);
}
// 每帧由宿主调用:把底层收到的字节解出并分发。
// XWebSocket 会把多条 WS 消息追加到同一个 bufferFrame 自带 payloadLen
// 所以这里按 FrameCodec 的线网格式从累计字节流中拆出 0..N 帧。
public void Pump()
{
if (_sock == null) return;
string err = null;
while (_sock.isDataRecv())
{
byte[] data = _sock.recv(ref err);
if (!string.IsNullOrEmpty(err)) { Debug.LogError("[MiniGameNetChannel] recv err: " + err); break; }
if (data == null || data.Length == 0) break;
_recvBuffer.AddRange(data);
}
while (TryPopFrame(out var frameBytes))
{
Frame f;
try { f = FrameCodec.Decode(frameBytes); }
catch (FormatException) { Debug.LogWarning("[MiniGameNetChannel] 畸形帧,丢弃"); continue; }
if (f.Channel == Channel.Game) OnGameMessage?.Invoke(new NetMessage(f.Opcode, f.Payload));
else OnFrameworkFrame?.Invoke(f);
}
}
private bool TryPopFrame(out byte[] frameBytes)
{
frameBytes = null;
if (_recvBuffer.Count < 4) return false; // channel + opcode + at least 1-byte payload len
int pos = 3;
uint payloadLen = 0;
int shift = 0;
while (true)
{
if (pos >= _recvBuffer.Count) return false;
if (shift > 28)
{
Debug.LogWarning("[MiniGameNetChannel] payloadLen varuint too long, clear buffer");
_recvBuffer.Clear();
return false;
}
byte b = _recvBuffer[pos++];
if (shift == 28 && (b & 0x70) != 0)
{
Debug.LogWarning("[MiniGameNetChannel] payloadLen overflow, clear buffer");
_recvBuffer.Clear();
return false;
}
payloadLen |= (uint)(b & 0x7F) << shift;
if ((b & 0x80) == 0) break;
shift += 7;
}
if (payloadLen > 1024 * 1024)
{
Debug.LogWarning("[MiniGameNetChannel] frame too large, clear buffer");
_recvBuffer.Clear();
return false;
}
int totalLen = pos + (int)payloadLen;
if (_recvBuffer.Count < totalLen) return false;
frameBytes = _recvBuffer.GetRange(0, totalLen).ToArray();
_recvBuffer.RemoveRange(0, totalLen);
return true;
}
}
}