2026-06-05
ULinkRPC exposes several kinds of public APIs. public means the type can be referenced by C#, but it does not always mean the type is a normal application entry point.
Use this page to decide which APIs your project should depend on directly.
Stable User API
These APIs are intended for regular application projects. They are the safest APIs to build long-lived code on.
- RPC contract attributes such as
[RpcService], [RpcMethod], [RpcNotificationContract], and [RpcNotification]. - Generated client facade and generated server binders.
RpcClientOptions.RpcClientRuntime when used through the generated client flow.RpcServerHostBuilder.RpcServerHost.RpcServerLimits.- Official transport constructors.
- Official serializer constructors.
RpcKeepAliveOptions.RpcException.RpcStatus.
User-facing tutorials and starter projects should stay inside this layer whenever possible.
2026-05-15
ULinkRPC is currently ready for a soft freeze: the main integration path, wire protocol direction, and package boundaries are mostly stable, but it is not yet ready to declare a full hard freeze or 1.0 API freeze.
This document records the current judgment and future optimization direction. It is not a one-time checklist; it is the basis for evaluating breaking changes before future releases.
Current Judgment
Areas that can be stabilized first:
2026-05-12
ULinkRPC.Starter can generate Godot 4.x C# clients. The Godot path shares the same Shared contracts, server project, and Roslyn Source Generator flow as the Unity path.
Create a Godot Project
Start with WebSocket + JSON:
dotnet tool install -g ULinkRPC.Starter
ulinkrpc-starter new --name MyGame --client-engine godot --transport websocket --serializer json
cd MyGame
dotnet run --project Server/Server/Server.csproj
The client is located at:
Open that directory with Godot 4.x, wait for Godot to generate and restore the C# solution, open Main.tscn, and click Play.
2026-05-12
This page only describes tuning directions supported by the current repository. The project has not published official benchmark numbers; do not state serializer or transport performance as absolute facts. Before production, measure with your own payloads, devices, network conditions, and engine versions.
For first integration, use websocket + json. JSON payloads make DTO shapes, field names, and server behavior easier to inspect. After the path is stable, evaluate memorypack or other transports.
2026-05-12
ULinkRPC contracts start from shared C# interfaces and DTOs. The source generator uses [RpcService], [RpcMethod], and DTO types to generate glue code for both sides. The core rule for versioning is: keep the wire shape compatible first, roll out gradually, then remove old fields or methods only after old clients are gone.
Do Not Reuse Stable IDs
[RpcService(id)] and [RpcMethod(id)] are part of protocol routing. Do not reuse a service id or method id that has already shipped. After deleting a method, keep a record of its id so old clients cannot route requests to a new meaning by accident.
2026-05-12
ULinkRPC security settings live at the frame layer. The main entry point is TransportSecurityConfig. It can enable compression and symmetric encryption, but it is not a complete replacement for authentication, authorization, or TLS.
What TransportSecurityConfig Does
When the client enables security through RpcClientOptions.UseSecurity(...), or the server enables it through RpcServerHostBuilder.UseSecurity(...), the runtime wraps the underlying transport with TransformingTransport.
Current options include:
EnableCompression: compress frames before sending.CompressionThresholdBytes: only try compression when the frame reaches this size, default 1024.MaxDecompressedFrameBytes: maximum decompressed frame size, default from RpcProtocolLimits.DefaultMaxDecompressedFrameBytes.EnableEncryption: enable symmetric encryption.EncryptionKey / EncryptionKeyBase64: symmetric key source.
Encryption currently uses AES-CBC plus HMAC-SHA256. HKDF derives an encryption key and a MAC key from the configured key. The receiver verifies HMAC; verification failure throws and causes the connection to fail.
2026-05-12
This page describes the error semantics currently implemented by the ULinkRPC runtime. It is not a guide to business error-code design; the business layer should still express expected failures in its own DTOs, such as login failure, insufficient inventory space, or a missing room.
Protocol Status
RPC response envelopes use RpcStatus for framework-level results:
Ok = 0: the service method returned successfully. The payload is the return DTO; void returns use an empty payload.NotFound = 1: the server could not find a handler for the requested serviceId:methodId.HandlerError = 2: the server handler failed or returned an invalid framework response.Overloaded = 3: the server could not accept the request because it is overloaded, such as when a request queue is full.BadRequest = 4: the request reached the RPC layer but was invalid for the target RPC contract.ProtocolError = 5: the peer violated the wire protocol or connection state machine.
These statuses only cover the framework layer. Do not map recoverable business failures to server exceptions; returning a business DTO such as LoginReply { Success, ErrorCode, Message } is more stable.
2026-05-12
The ULinkRPC runtime uses background Tasks for receive, push, request dispatch, and keepalive. It does not automatically switch back to the Unity, Tuanjie, or Godot main thread.
Client Threading Model
RpcClientRuntime.StartAsync starts:
- receive loop: reads transport frames and handles responses, pushes, and keepalive ping / pong.
- notification loop: reads server notification frames from an internal queue and invokes registered notification handlers.
- keepalive loop: sends pings and detects timeouts when keepalive is enabled.
Notification handlers run on the runtime’s notification loop. The code comments are explicit: notification handlers are not marshaled to the Unity main thread.
2026-05-12
ULinkRPC splits connection lifetime into two layers: the low-level ITransport owns connection and frame I/O, while RpcClientRuntime / RpcSession own RPC request, response, notification, keepalive, and shutdown behavior.
Client Lifecycle
The generated RpcClient facade holds an internal RpcClientRuntime. When ConnectAsync is called, the runtime:
- Calls the transport’s
ConnectAsync. - Initializes keepalive state.
- Starts the receive loop.
- Starts the notification loop.
- Starts the keepalive loop if keepalive is enabled.
The same runtime can only be started once; repeated starts throw InvalidOperationException. After disconnecting or disposing, the same runtime must not be reused. The application layer should dispose the current client, create a new transport, serializer, options object, and generated client, then reconnect.
2026-05-12
ULinkRPC is responsible for communication framework behavior, not for being a complete application server framework.
The framework handles:
- transport integration and frame I/O
- frame encoding, compression, encryption, and limits
- session lifetime
- request / response / push dispatch
- keepalive and connection shutdown semantics
- serializer boundaries
The application layer handles:
- user login, account systems, and token lifetime
- request-level authorization and business permissions
- business error codes and recoverable failures
- automatic reconnect, state recovery, and request replay
- DTO versioning strategy and gradual rollout
- Unity, Tuanjie, or Godot main-thread dispatch
Authentication and Authorization
TransportSecurityConfig can provide frame-level compression and symmetric encryption, but it does not verify remote identity and does not include users, roles, tenants, resource ownership, or policy systems.
2026-05-12
ULinkRPC fits projects that share contracts between a C# server and a C# game client. Its core value is building a strongly typed communication boundary around the same C# interfaces and DTOs on both sides, while keeping transport, serializer, and runtime integration on a fixed workflow.
Good Fits for ULinkRPC
Your server is .NET, and your client is Unity, Tuanjie Engine, or Godot C#.
You want Shared contracts to be the single source of truth, with both server and client compiling communication glue from the same C# DTOs and interfaces.
2026-03-18
The previous article focused on usage:
Once you consider putting ULinkRPC into a real project, the next questions are usually about design:
- Why center the framework on shared C# contracts?
- How is it fundamentally different from hand-written message ids plus
switch dispatch? - How does bidirectional communication fit onto one connection?
- How are Transport, Serializer, Runtime, and Source Generation decoupled?
- Which layer owns server callbacks, client pushes, request/response, keepalive, compression, and encryption?
This article does not cover environment setup. It explains the design itself: why the layers exist, why C# contracts are the communication boundary, and how bidirectional communication runs over one connection.
2026-03-15
If you want to start a new two-sided C# project today, ULinkRPC.Starter is the recommended path.
It is no longer just an initialization scaffold. It is the project tool for ULinkRPC:
- Create new projects with
ulinkrpc-starter new - Generate projects with Roslyn Source Generator configuration, so later contract changes refresh server and client glue during normal compilation
It generates all of this at once:
- a
Shared contract project - a
Server project and solution - a
Client skeleton for Unity 2022, Tuanjie Engine, or Godot 4.x - a default
Ping contract, service implementation, and client test entry point ULinkRPC.Analyzers source generator configuration
In other words, the recommended starting point is: