PubSubLib.Client 1.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package PubSubLib.Client --version 1.1.0
                    
NuGet\Install-Package PubSubLib.Client -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PubSubLib.Client" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PubSubLib.Client" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="PubSubLib.Client" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PubSubLib.Client --version 1.1.0
                    
#r "nuget: PubSubLib.Client, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PubSubLib.Client@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PubSubLib.Client&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=PubSubLib.Client&version=1.1.0
                    
Install as a Cake Tool

Client — PubSubLib.Client

Package chạy trong game client (Unity / .NET). Tự động ping định kỳ để đồng bộ trạng thái với PubSub server, nhận event để tạo/hủy GameObject. Là một IClientModule cắm vào MyConnection client.

Mục lục


Vai trò

Game Client (PubSubLib.Client) ◄── TCP ──► Router ◄── NATS ──► PubSub Server

Client làm 2 nhiệm vụ:

Nhiệm vụ Cơ chế
Đồng bộ trạng thái Tick() định kỳ build PingUnitsCmd với version map → gửi UDP PubSub.Cmd
Nhận event Subscribe TCP PubSub.EvtHandleBatchEnter/HandleSyncEnter/HandleBatchLeave/HandleSyncLeave → gọi IProvider tạo/hủy GameObject

Client không tự quản lý vị trí unit — mọi thay đổi vị trí do server authoritative quyết định và gửi về qua event.


Cài đặt

<PackageReference Include="PubSubLib.Client" Version="1.1.0" />

Package target netstandard2.1 — tương thích Unity IL2CPP.

Dependencies:

  • PubSubLib.Contracts — protobuf message types
  • Google.Protobuf (3.34.1) — deserialize message
  • MyConnection (1.0.4) — giao tiếp TCP/UDP với Router

Kiến trúc

                         PubSubClientModule                  PubSubClient
┌──────────────┐        ┌──────────────────────┐        ┌─────────────────────┐
│ MyConnection │  TCP   │ IClientModule        │        │ IPubSubClient       │
│   IClient    │◄──────►│                      │───────►│                     │
│              │        │ SetIClient(client)   │        │ Tick()              │
│              │        │   → SubscribeTcp     │        │ MoveWatcher()       │
│              │        │     "PubSub.Evt"     │        │ AddProvider()       │
│              │        │                      │        │                     │
│              │  UDP   │                      │        │ _units: Dictionary  │
│              │◄───────│                      │        │   (Id,Type) → Unit  │
│              │        │                      │        │ _providers: Dict    │
│              │        │                      │        │   "type" → IProvider│
└──────────────┘        └──────────────────────┘        └─────────────────────┘
                                   │
                                   ▼
                            IProvider (do bạn implement)
                            ├─ HeroProvider  → CreateObject / DestroyObject
                            └─ MobProvider   → CreateObject / DestroyObject

API Reference

IPubSubClient

public interface IPubSubClient
{
    void Tick();
    void MoveWatcher(Vector2 position, float radius);
    IPubSubClient AddProvider(IProvider provider);
}
Phương thức Mô tả
Tick() Gọi mỗi frame. Kiểm tra thời gian ping → nếu đến hạn, build PingUnitsCmd và gửi UDP. Đồng thời dọn unit dead (GC collected).
MoveWatcher(pos, radius) Gửi MoveWatcherCmd lên server. Deduplicated — chỉ gửi khi vị trí hoặc bán kính thực sự thay đổi.
AddProvider(provider) Đăng ký factory cho 1 UnitType. Provider được gọi khi client nhận BatchEnter/SyncEnter để tạo GameObject, và BatchLeave/SyncLeave để hủy.

IPubSubClientModule

public interface IPubSubClientModule : IClientModule
{
    static IPubSubClientModule Create(Config config);
    IPubSubClient Get();
}

Module cắm vào MyConnection client:

Phương thức Mô tả
Create(config) Tạo module với cấu hình ping
Get() Lấy IPubSubClient để gọi Tick(), MoveWatcher(), AddProvider()

IProvider

public interface IProvider
{
    string UnitType { get; }

#if UNITY_ENGINE
    UnityEngine.GameObject CreateObject(long unitId, int version, byte[] data);
    void DestroyObject(long unitId, UnityEngine.GameObject obj);
    void OnEvent(long unitId, UnityEngine.GameObject obj, string eventName, byte[] data, EventMeta meta);
#else
    GameObjectTest CreateObject(long unitId, int version, byte[] data);
    void DestroyObject(long unitId, GameObjectTest obj);
    void OnEvent(long unitId, GameObjectTest obj, string eventName, byte[] data, EventMeta meta);
#endif
}

Factory pattern cho từng loại unit. Bạn implement interface này cho mỗi UnitType:

Method Khi nào gọi Mô tả
CreateObject(unitId, version, data) Nhận BatchEnter hoặc SyncEnter Tạo GameObject từ prefab, áp dụng data, trả về instance
DestroyObject(unitId, obj) Nhận BatchLeave hoặc SyncLeave Hủy GameObject, cleanup resource
OnEvent(unitId, obj, eventName, data, meta) Nhận UnitEventMsg Xử lý event từ unit. meta.Transport cho biết event đến qua TCP hay UDP
UnitType String type khớp với unit.Type trên server (vd: "hero", "mob")

EventMeta

public enum EventTransport { Tcp, Udp }

public readonly struct EventMeta
{
    public EventTransport Transport { get; }
    public EventMeta(EventTransport transport);
}

Dùng trong IProvider.OnEvent() để biết event đến qua kênh nào:

  • EventTransport.Tcp — reliable, đảm bảo delivery
  • EventTransport.Udp — best-effort, có thể mất gói

Unity build: dùng #if UNITY_ENGINECreateObject trả về UnityEngine.GameObject, DestroyObject nhận UnityEngine.GameObject. Test/Dev build: dùng GameObjectTest (stub class trong package).

Config

public class Config
{
    public int PingIntervalMs { get; set; } = 1000;
}
Param Default Mô tả
PingIntervalMs 1000 Tần suất ping (ms). Tick() chỉ gửi ping khi thời gian từ lần ping trước ≥ giá trị này.

Cách dùng

Setup cơ bản

using MyConnection;
using PubSubLib.Client;

// 1. Tạo MyConnection client (kết nối đến Router)
var client = IClient.Create(new ClientConfig
{
    tcpServer = "127.0.0.1:9090",
    udpServer = "127.0.0.1:9091",
    udpPingIntervalMs = 5000,
    udpPingTimeoutMs = 15000
});

// 2. Tạo PubSub client module
var pubSubModule = IPubSubClientModule.Create(new Config
{
    PingIntervalMs = 1000  // ping mỗi 1 giây
});

// 3. Đăng ký Provider cho từng loại unit
pubSubModule.Get()
    .AddProvider(new HeroProvider())
    .AddProvider(new MobProvider())
    .AddProvider(new ItemProvider());

// 4. Gắn module vào client
client.AddModule(pubSubModule);

// 5. Login (UserId = watcherId)
await client.Login(() => new LoginBody { UserId = "player_42" });
await client.ConnectServer();

// === Game Loop ===
// Mỗi frame:
//   pubSubModule.Get().Tick();                          // ping định kỳ
//   pubSubModule.Get().MoveWatcher(playerPos, 200f);     // cập nhật vị trí watcher

Implement IProvider

public class HeroProvider : IProvider
{
    public string UnitType => "hero";

    public GameObjectTest CreateObject(long unitId, int version, byte[] data)
    {
        // Unity: Instantiate prefab, set position từ data, v.v.
        var go = new GameObjectTest();
        Console.WriteLine($"[Client] Tạo hero {unitId} v{version}, data={data?.Length ?? 0} bytes");
        return go;
    }

    public void DestroyObject(long unitId, GameObjectTest obj)
    {
        // Unity: Destroy(obj.gameObject)
        Console.WriteLine($"[Client] Hủy hero {unitId}");
    }

    public void OnEvent(long unitId, GameObjectTest obj, string eventName, byte[] data, EventMeta meta)
    {
        if (meta.Transport == EventTransport.Udp)
        {
            // Xử lý event UDP (best-effort, có thể bỏ qua nếu FPS thấp)
        }
        else
        {
            // Xử lý event TCP (reliable, luôn được gọi)
        }
    }
}

Game loop đầy đủ

// Unity MonoBehaviour
public class GameManager : MonoBehaviour
{
    private IPubSubClient _pubSubClient;

    void Start()
    {
        // ... setup client, login ...
        var module = IPubSubClientModule.Create(new Config { PingIntervalMs = 500 });
        _pubSubClient = module.Get();
        _pubSubClient.AddProvider(new HeroProvider());
        client.AddModule(module);
    }

    void Update()
    {
        // Gọi Tick() mỗi frame — nội bộ tự kiểm tra thời gian ping
        _pubSubClient.Tick();

        // Gọi MoveWatcher() mỗi frame — nội bộ tự deduplicate
        _pubSubClient.MoveWatcher(transform.position, sightRadius);
    }
}

Luồng xử lý nội bộ

1. Tick / Ping cycle

Update() mỗi frame
  │
  ▼ Tick()
  ├─ Kiểm tra _client != null?
  ├─ Kiểm tra elapsed >= _pingIntervalMs?
  │   └─ Không → return (skip ping lần này)
  │
  ├─ CleanDeadUnits()
  │   ├─ Duyệt _units, kiểm tra IsAlive
  │   ├─ Unit dead (WeakReference mất) → DestroyObject + xóa khỏi _units
  │   └─ Unit alive → tiếp tục
  │
  ├─ Build PingUnitsCmd
  │   ├─ Gom tất cả unit theo type → TypeGroup
  │   │   hero: [id=42 v=7, id=43 v=3]
  │   │   mob:  [id=100 v=1]
  │   └─ cmd.Units.Add(groups)
  │
  └─ _client.SendOnUdp("PubSub.Cmd", PubSubCommand { PingUnits = cmd })
      │
      ▼ UDP ──► Router ──► NATS ──► Server // WatcherPingUnits()

CleanDeadUnits (PubSubClient.cs:162-180):

  • Mỗi lần Tick(), trước khi build ping, client duyệt toàn bộ _units
  • Nếu unit.IsAlive == false (GameObject bị GC collect hoặc Destroy): gọi DestroyObject trên provider, xóa khỏi _units
  • Đảm bảo ping chỉ chứa unit thực sự còn sống

Deduplication: Tick() tự kiểm tra _lastPingTimestamp — chỉ gửi ping khi đủ PingIntervalMs. Bạn có thể gọi Tick() mỗi frame mà không lo spam.

2. Nhận event từ server

Client subscribe cả TCP và UDP topic PubSub.Evt để nhận event từ server, dispatch theo loại event:

TCP/UDP "PubSub.Evt" ──► PubSubClientModule.OnEvent()
                        │
                        ▼ switch evt.EvtCase
                    ┌──────────────────────────────────────────────┐
                    │ BatchEnterMsg                                │
                    │   → HandleBatchEnter(msg)                    │
                    │   → provider.CreateObject(unitId, ver, data) │
                    │   → _units[(id, type)] = new Unit(...)       │
                    │                                              │
                    │ SyncEnterMsg                                 │
                    │   → foreach item in msg.Units                │
                    │   → HandleSyncEnter(msg)                     │
                    │   → provider.CreateObject(...)               │
                    │   → _units[(id, type)] = new Unit(...)       │
                    │                                              │
                    │ BatchLeaveMsg                                │
                    │   → HandleBatchLeave(msg)                    │
                    │   → provider.DestroyObject(unitId, target)   │
                    │   → _units.Remove((id, type))                │
                    │                                              │
                    │ SyncLeaveMsg                                 │
                    │   → foreach group in msg.Keys                │
                    │   → foreach unitId in group.UnitIds          │
                    │   → HandleSyncLeave(msg)                     │
                    │   → provider.DestroyObject(...)              │
                    │   → _units.Remove((id, type))                │
                    │                                              │
                    │ UnitEventMsg                                 │
                    │   → HandleUnitEvent(msg)                     │
                    │   → provider.OnEvent(id, go, name, data, meta)│
                    └──────────────────────────────────────────────┘

Luồng điển hình:

1. Server tạo unit → BatchEnter → Client nhận → CreateObject → GameObject xuất hiện
2. Client Tick() ping → gửi version map → Server so sánh
3. Server thấy version cũ → SyncEnter với version mới → Client cập nhật
4. Server thấy unit đã mất → SyncLeave → Client DestroyObject → GameObject biến mất

3. MoveWatcher

MoveWatcher(position, radius)
  │
  ├─ Lưu _position = position, _radius = radius
  ├─ Kiểm tra dedup: vị trí và bán kính có khác lần gửi trước?
  │   └─ Không → return (không gửi lại)
  │
  ├─ _lastSentPos = position, _lastSentRadius = radius
  └─ _client.SendOnUdp("PubSub.Cmd", PubSubCommand { MoveWatcher = cmd })
      │
      ▼ UDP ──► Router ──► NATS ──► Server // pubSub.MoveWatcher()

Deduplication: So sánh (x, y, radius) hiện tại với lần gửi trước. Chỉ gửi khi thực sự thay đổi — tránh spam network khi nhân vật đứng yên.

4. Unit tracking với WeakReference

Client _units: Dictionary<(long Id, string Type), Unit>

Unit (client-side):
  ├─ Id, Version, Type
  └─ WeakReference<object> → GameObject

Vòng đời:
  1. Nhận BatchEnter/SyncEnter → CreateObject → Unit(obj) với WeakReference
  2. Tick() → CleanDeadUnits → kiểm tra Unit.IsAlive
     ├─ Alive (WeakReference còn target) → giữ lại trong ping
     └─ Dead (GC collected / Destroy) → DestroyObject + xóa khỏi _units
  3. Nhận BatchLeave/SyncLeave → DestroyObject + xóa khỏi _units

Cơ chế an toàn GC:

  • Client giữ WeakReference tới GameObject, không phải strong reference
  • Nếu GameObject bị destroy từ bên ngoài (vd: scene unload), IsAlive → false
  • Lần Tick() tiếp theo, CleanDeadUnits phát hiện và dọn dẹp
  • Server sẽ nhận ping thiếu unit → SyncLeave (nếu unit thực sự không còn trên server) hoặc SyncEnter (re-sync nếu unit vẫn còn)

Ví dụ hoàn chỉnh

Full setup Client + Router + Server: xem PubSubTestAll.cs — test FullStack_ClientKillsUnit_ServerResyncs mô phỏng client mất unit → server re-sync.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.8.9 39 7/6/2026
1.8.8 37 7/6/2026
1.8.7 41 7/6/2026
1.8.6 44 7/6/2026
1.8.5 92 6/24/2026
1.8.4 93 6/24/2026
1.8.2 91 6/23/2026
1.8.1 103 6/23/2026
1.8.0 96 6/21/2026
1.7.0 100 6/20/2026
1.6.0 101 6/19/2026
1.5.0 102 6/19/2026
1.4.0 104 6/19/2026
1.3.0 104 6/19/2026
1.2.0 104 6/19/2026
1.1.0 104 6/18/2026