ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpacetimeDB C 控制台聊天客户端实战:chat-console-cs 模板源码级拆解与绑定重生成指南

SpacetimeDB C 控制台聊天客户端实战:chat-console-cs 模板源码级拆解与绑定重生成指南 SpacetimeDB C# 控制台聊天客户端实战chat-console-cs 模板源码级拆解与绑定重生成指南【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDBtemplates/chat-console-cs是 SpacetimeDB 仓库中内置的 C#「Quickstart client」模板一个完整的终端聊天程序配套同目录下的 C# 服务端模块spacetimedb/Lib.cs与自动生成的强类型客户端绑定module_bindings/。本文以该模板为线索从服务端表与 reducer 定义、客户端连接与事件回调、生成绑定的运行机制到gen-quickstart.sh的绑定重生成流程逐一拆解。读完你既能复现并运行这个聊天示例也能掌握在修改模块后重新生成 C# 客户端绑定的完整方法。一、模板定位一个客户端 模块 绑定三位一体的开箱示例官方文档templates/chat-console-cs/README.md对它的定位非常直白这是一个 quickstart 客户端其服务端逻辑对应 SpacetimeDB 官方仓库中的quickstart-chat模块。在当前仓库内该模块的服务端源码就位于模板自带的 spacetimedb/Lib.cs客户端绑定则由 module_bindings 提供。整个模板的目录结构如下templates/chat-console-cs/ ├── spacetimedb/ # 服务端模块编译为 WASI WebAssembly │ ├── StdbModule.csproj │ ├── Lib.cs # 表与 reducer 定义 │ └── global.json ├── module_bindings/ # 自动生成的 C# 客户端绑定勿手改 │ ├── SpacetimeDBClient.g.cs # 连接、订阅、事件上下文、reducer 分发 │ ├── Reducers/ │ │ ├── SendMessage.g.cs │ │ └── SetName.g.cs │ ├── Tables/ │ │ ├── Message.g.cs │ │ └── User.g.cs │ └── Types/ │ ├── Message.g.cs │ └── User.g.cs ├── Program.cs # 客户端控制台程序入口 ├── client.csproj # 客户端工程 └── README.md # 模板说明它把 SpacetimeDB 开发中三个典型角色放在一个目录里服务端模块定义数据与业务、客户端消费实时数据、触发 reducer、生成绑定连接二者的强类型桥梁。README.md除了给出绑定重生成指引外其余内容全部体现在Program.cs与Lib.cs的实际代码中下面逐层展开。二、服务端模块表结构与 reducerspacetimedb/Lib.cs服务端逻辑定义在 templates/chat-console-cs/spacetimedb/Lib.cs通过[Table]属性声明两张公开表[Table(Accessor User, Public true)] public partial class User { [PrimaryKey] public Identity Identity; public string? Name; public bool Online; } [Table(Accessor Message, Public true)] public partial class Message { public Identity Sender; public Timestamp Sent; public string Text ; }User表以IdentitySpacetimeDB 的身份类型为主键记录昵称Name可空未设置昵称的用户用身份短前缀显示与在线状态OnlineMessage表记录每条消息的发送者Sender、发送时间SentTimestamp类型客户端用它做历史消息排序与正文Text。2.1 消息与昵称 reducer模块通过[Reducer]定义两个业务 reducerLib.cs[Reducer] public static void SetName(ReducerContext ctx, string name) { name ValidateName(name); if (ctx.Db.User.Identity.Find(ctx.Sender) is User user) { user.Name name; ctx.Db.User.Identity.Update(user); } } [Reducer] public static void SendMessage(ReducerContext ctx, string text) { text ValidateMessage(text); Log.Info(text); ctx.Db.Message.Insert( new Message { Sender ctx.Sender, Text text, Sent ctx.Timestamp } ); }要点reducer 是唯一写库入口SetName通过主键索引ctx.Db.User.Identity.Find(ctx.Sender)定位当前身份对应的User更新后调用Update持久化SendMessage直接Insert一条消息Sender取ctx.Sender、Sent取ctx.Timestamp均由运行时注入客户端不可伪造输入校验两个私有方法ValidateName/ValidateMessage拒绝空字符串分别抛Exception与ArgumentException客户端对应回调里会根据 reducer 失败状态打印错误服务端Log.Info(text)会把消息写入模块日志可用spacetime logs查看。2.2 连接生命周期 reducer除了业务 reducer模块还利用 SpacetimeDB 的内置 reducer 种类维护User.Online状态Lib.cs[Reducer(ReducerKind.ClientConnected)] ClientConnected老用户回来则把Online置true新用户则插入一条Online true、Name null的记录[Reducer(ReducerKind.ClientDisconnected)] ClientDisconnected把Online置false查不到用户时输出Log.Warn警告。这正是客户端「XX is online / XX connected / XX disconnected」通知的数据来源也说明User.OnUpdate回调会同时收到改名与在线状态两类变更。三、客户端主流程Program.cs 逐段拆解客户端入口 templates/chat-console-cs/Program.cs 是完整可运行的顶层语句程序核心流程如下。3.1 Main 的整体编排void Main() { AuthToken.Init(.spacetime_csharp_quickstart); // 初始化本地凭证 DbConnection? conn ConnectToDB(); // 构建连接 RegisterCallbacks(conn); // 注册事件回调 var cancellationTokenSource new CancellationTokenSource(); var thread new Thread(() ProcessThread(conn, cancellationTokenSource.Token)); thread.Start(); // 后台处理线程 InputLoop(); // 主线程读终端输入 cancellationTokenSource.Cancel(); thread.Join(); }四件事初始化认证 → 建连 → 注册回调 → 双线程跑起来后台线程驱动网络帧与命令发送主线程读用户输入。3.2 连接参数与认证连接地址与数据库名通过环境变量注入带默认值Program.csstring HOST Environment.GetEnvironmentVariable(SPACETIMEDB_HOST) ?? http://localhost:3000; string DB_NAME Environment.GetEnvironmentVariable(SPACETIMEDB_DB_NAME) ?? quickstart-chat; conn DbConnection.Builder() .WithUri(HOST) .WithDatabaseName(DB_NAME) .WithToken(AuthToken.Token) .OnConnect(OnConnected) .OnConnectError(OnConnectError) .OnDisconnect(OnDisconnected) .Build();DbConnection.Builder()是构建器模式WithUri指定实例地址、WithDatabaseName指定模块/数据库名、WithToken携带认证令牌并注册三个连接生命周期回调。AuthToken.Init(.spacetime_csharp_quickstart)指定本地凭证文件名首次连接成功后令牌被持久化见下之后重启客户端无需重复登录。3.3 连接成功后的订阅OnConnected回调里做两件事Program.cs保存本地身份与令牌然后发起全表订阅void OnConnected(DbConnection conn, Identity identity, string authToken) { local_identity identity; AuthToken.SaveToken(authToken); conn.SubscriptionBuilder() .OnApplied(OnSubscriptionApplied) .SubscribeToAllTables(); }SubscribeToAllTables()会一次性订阅模块暴露的所有表本模板即message与user并把全量行复制到客户端本地缓存OnApplied回调在订阅生效时触发用于打印历史消息。OnConnectError打印异常OnDisconnected区分正常/异常断开——异常时打印异常对象正常时提示 Disconnected normally.3.4 事件回调注册RegisterCallbacks把本地方法与数据库事件绑定Program.csconn.Db.User.OnInsert User_OnInsert; conn.Db.User.OnUpdate User_OnUpdate; conn.Db.Message.OnInsert Message_OnInsert; conn.Reducers.OnSetName Reducer_OnSetNameEvent; conn.Reducers.OnSendMessage Reducer_OnSendMessageEvent;表事件Db.User/Db.MessageOnInsert在行插入时触发包括订阅复制和别的客户端写入OnUpdate在行变更时触发reducer 事件Reducers.OnSetName/Reducers.OnSendMessage本连接发起的 reducer 调用返回后触发用于检查调用是否失败。各回调的行为User_OnInsert新用户若Online为真打印XX is onlineProgram.csUser_OnUpdate对比新旧行昵称变了打印XX renamed to YY在线状态变了打印XX connected./XX disconnected.Program.csMessage_OnInsert过滤掉订阅应用阶段插入的消息ctx.Event is not EventReducer.SubscribeApplied因为历史消息统一在OnSubscriptionApplied里排序打印避免乱序Program.cs两个 reducer 回调仅当调用者是本地身份e.CallerIdentity local_identity且状态为Status.Failed(var error)时打印失败原因Program.cs。UserNameOrIdentity是个巧妙的兜底用户没设置昵称时取Identity字符串前 8 个字符作为显示名Program.cs。3.5 双线程模型输入循环 处理循环为了让「读终端」不阻塞「收网络消息」程序拆成两个线程处理线程ProcessThreadProgram.cs——100ms 一个周期驱动网络帧并消费命令队列while (!ct.IsCancellationRequested) { conn.FrameTick(); // 推进连接收发消息、派发回调 ProcessCommands(conn.Reducers); Thread.Sleep(100); }输入线程主线程InputLoopProgram.cs——逐行读 stdin解析成命令入队if (input.StartsWith(/name )) input_queue.Enqueue((name, input[6..])); // /name 昵称 else input_queue.Enqueue((message, input)); // 其余全部当消息命令队列用ConcurrentQueue(string Command, string Args)保证跨线程安全。ProcessCommands从队列取命令并调用对应 reducermessage→reducers.SendMessage(args)name→reducers.SetName(args)Program.cs。整体交互协议非常朴素输入含义/name 昵称调用SetNamereducer 设置昵称其他任意文本调用SendMessagereducer 发送消息3.6 订阅应用时的历史消息回放OnSubscriptionAppliedProgram.cs打印Connected后把本地缓存里的Message按Sent升序输出foreach (Message message in tables.Message.Iter().OrderBy(item item.Sent)) PrintMessage(tables, message);PrintMessage用tables.User.Identity.Find(message.Sender)反查发送者昵称查不到就显示unknownProgram.cs。这一步与Message_OnInsert的SubscribeApplied过滤配合保证历史消息按时间顺序整齐回放、实时消息即时刷出。四、生成的客户端绑定module_bindings 的运行机制module_bindings 下的.g.cs文件全部由 CLI 自动生成文件头明确警告「EDITS TO THIS FILE WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD」生成版本为spacetimedb cli 2.6.0 (commit 6ad1c629b52ca9b6c06f9e2a90fb8ed8639c3a13)见 SpacetimeDBClient.g.cs。4.1 reducer 的调用与分发以SendMessage为例Reducers/SendMessage.g.cs生成代码做了三件事public delegate void SendMessageHandler(ReducerEventContext ctx, string text); public event SendMessageHandler? OnSendMessage; // 回调注册点 public void SendMessage(string text) // 客户端调用入口 { conn.InternalCallReducer(new Reducer.SendMessage(text)); }调用reducers.SendMessage(...)时参数被打包成Reducer.SendMessage参数对象经InternalCallReducer发送到服务端服务端执行结果返回后DbConnection.Dispatch通过switch把事件路由回InvokeSendMessage进而触发OnSendMessage事件SpacetimeDBClient.g.csReducer.SendMessage args Reducers.InvokeSendMessage(eventContext, args), Reducer.SetName args Reducers.InvokeSetName(eventContext, args),线协议上的 reducer 名称由IReducerArgs.ReducerName提供如send_message与 C# 方法名PascalCase自动转换客户端代码无需关心。4.2 订阅与上下文类型体系RemoteTables构造函数把Message、User两个表句柄注册进连接SpacetimeDBClient.g.csTables/Message.g.cs里对应MessageHandle表句柄与MessageCols列访问器Sender/Sent/Text是强类型查询的基础SubscriptionBuilder.SubscribeToAllTables()实际由QueryBuilder.AllTablesSqlQueries()生成针对message、user两张表的 SQL 查询串SpacetimeDBClient.g.cs生成代码还定义了完整的上下文类型体系EventContext表事件、ReducerEventContextreducer 事件含Event.CallerIdentity、Event.Status、SubscriptionEventContext订阅应用、ErrorContext订阅/连接错误与ProcedureEventContext过程调用它们都实现了IRemoteDbContext统一暴露Db本地缓存表、Reducers、Procedures、Identity、Disconnect()等能力SpacetimeDBClient.g.cs。简单说改模块 → 重生成绑定 → 客户端代码自动获得新的强类型 API这是该模板「快速起步」的核心体验。五、重新生成绑定gen-quickstart.sh 全流程模板 README 给出了重生成绑定的标准做法将 SpacetimeDB 仓库克隆到本仓库旁边然后在sdks/csharp目录下执行tools~/gen-quickstart.sh注意tools~目录名自带波浪号属于仓库约定命名。在当前仓库内这个脚本位于 sdks/csharp/tools~/gen-quickstart.sh其核心命令是cargo spacetime generate -y -l csharp \ -o $STDB_PATH/templates/chat-console-cs/module_bindings \ --module-path $STDB_PATH/templates/chat-console-cs/spacetimedb即以templates/chat-console-cs/spacetimedb为模块源码为 C# 语言-l csharp生成绑定覆盖写入templates/chat-console-cs/module_bindings。任何对Lib.cs中表结构、reducer 签名的修改都要重跑此脚本否则客户端绑定与服务端 schema 会脱节。5.1 可选的 .NET 版本参数脚本还支持一个可选参数DOTNET_VERSION用来控制生成时所使用的 SDK 版本并写入对应的global.json脚本会自动备份并在退出时恢复原有global.json参数写入的 global.json说明8{sdk:{version:8.0.100,rollForward:latestFeature}}.NET 8 路线10{sdk:{version:10.0.100,rollForward:latestMinor}}.NET 10 路线传入版本时脚本还会追加--build-options--dotnet-version 版本让生成过程按指定 .NET 版本编译模块以提取 schema。从源码看write_dotnet_global_json会临时替换 templates/chat-console-cs/spacetimedb/global.json并用trap restore_global_jsons EXIT保证无论成功失败都会还原文件。5.2 前提条件脚本依赖cargo spacetimeSpacetimeDB 的 CLI 工具链仓库内实现位于 crates/cli以及本仓库sdks/csharp下的 C# SDK 工程。执行前建议先确认仓库能正常编译、cargo spacetime可用再运行脚本。六、工程配置细节两个 csproj 的分工模板用两个独立工程分隔「服务端模块」与「客户端应用」client.csproj客户端控制台程序。OutputTypeExe、TargetFrameworknet8.0启用Nullable关闭ImplicitUsings通过ProjectReference引用 SDK 工程 sdks/csharp/SpacetimeDB.ClientSDK.csprojEmitCompilerGeneratedFilestrue便于调试生成的中间代码用Compile Removespacetimedb/** /把模块源码排除在客户端编译之外避免两个工程互相污染。spacetimedb/StdbModule.csproj服务端模块工程。TargetFrameworksnet8.0;net10.0RuntimeIdentifierwasi-wasm模块编译为 WebAssembly 运行于数据库进程内引用SpacetimeDB.Runtime2.10.*服务端运行时 API区别于客户端 SDKnet10.0 或开启EXPERIMENTAL_WASM_AOT时启用PublishTrimmed、SelfContained并引入Microsoft.DotNet.ILCompiler.LLVM实验性包用于 WASI 的 AOT 编译。服务端模块的 WASI 工作负载安装方式可参考同仓库其他 C# 模板的说明如 templates/basic-cs/README.md 中的dotnet workload install wasi-experimental。七、从零运行这个聊天示例结合模板源码与仓库内通用工作流完整跑通的路径如下前提.NET 8 SDK、SpacetimeDB CLI、WASI 实验性工作负载均已就绪启动本地实例运行 SpacetimeDB 本地服务器默认监听localhost:3000——与客户端默认HOST一致发布模块用spacetime publish发布templates/chat-console-cs/spacetimedb模块发布时的数据库名须与客户端默认值一致quickstart-chat否则要设置环境变量对齐运行客户端cd templates/chat-console-cs dotnet run首次连接会生成并持久化本地身份令牌订阅应用后终端打印Connected与历史消息回放 4.交互输入/name Alice设置昵称触发SetName直接输入文字回车即发送消息触发SendMessage打开第二个终端再跑一个客户端即可看到对方上线/发消息实时刷出——这正是User/Message表订阅 事件回调的实时性体现 5.排查手段可使用spacetime sql直接查询User、Message表用spacetime logs查看模块日志Log.Info输出。7.1 环境变量一览环境变量默认值作用SPACETIMEDB_HOSThttp://localhost:3000服务端实例地址HTTP/WS 基址SPACETIMEDB_DB_NAMEquickstart-chat连接的模块/数据库名八、延伸模板体系与二次开发思路chat-console-cs与 templates/basic-cs入门 CRUD 模板可用spacetime dev --template basic-cs快速初始化共同构成 C# 模板梯度basic-cs 教你表与 reducer 的最小闭环chat-console-cs 则完整演示了订阅、事件回调、多线程与历史回放这些真实客户端必备能力。二次开发时最值得关注的扩展点加表/加 reducer改spacetimedb/Lib.cs后重跑 gen-quickstart.shmodule_bindings会新增对应的RemoteTables表句柄与RemoteReducers调用方法Program.cs里照User_OnInsert/Reducer_OnSendMessageEvent的模式注册回调即可收窄订阅范围把SubscribeToAllTables()换成SubscriptionBuilder.AddQuery(...)或Subscribe(sqls)只同步客户端关心的行减少带宽与内存占用生成代码的SubscriptionBuilder已提供完整 API改造为 Web/移动端控制台的「事件回调 本地缓存」模型与 SDK 其他语言客户端一致逻辑可平滑迁移到 sdks/typescript 或 sdks/csharp 的其他宿主。总之templates/chat-console-cs是一份可运行、可扩展、可重生成的 C# 客户端范式——理解它就理解了 SpacetimeDB「客户端强类型订阅 reducer 驱动」的核心开发模型。【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表