Rust 元宇宙 14 —— 创建角色和同步

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上一章我们成功的将客户端连接到元宇宙服务器中,并实现了最简单的 登录协议 —— 在 http/https 头中附加客户端获得的认证 token,也就是 agent 服务支持 内部的 rpc 协议:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"rust"},"content":[{"type":"text","text":"#[derive(Debug, Clone)]\npub enum AgentRequest {\n Login(String, crossbeam_channel::Sender),\n TokenMsg(String, AgentMsg),\n IDMsg(u32, AgentMsg),\n Notify(NotifyType)\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Login协议附带了 Websocket 接收数据的 Sender,这样以后服务器的数据可以通过这个 Sender 发送到 保持这个 Websocket 的异步线程,并转发到连接上来的客户端,上一章包含了简单的转发代码。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客户端连接上服务器之后,有下面几种状态,适用不同的消息类型。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"未加载角色,角色没有进入游戏世界,没有获得 玩家的唯一ID,这个时候需要使用基于 token 的 msg 比如说创建和选择角色。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"角色已加载并进入游戏世界,使用基于ID的msg","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"来自元宇宙的通知消息,Notify","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一个最典型的 TokenMsg是创建角色,为说明概念,我们这里仅设定角色 暱称,创建角色,随机设置位置,并让角色进入 0 号世界中,创建角色的代码如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"rust"},"content":[{"type":"text","text":" pub fn create(&mut self, token: &str, nick: &str)-> u32 {\n let pos = Position{x: (rand::random::() % 1024) as f32, y: (rand::random::() % 1024) as f32};\n let id = self.agents.add(token.into(), Player{nick: nick.into(), world: 0, pos});\n self.save(token);\n id\n }","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"加载和存储的代码我们已经在 12 章列出。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了让客户端可以创建角色,我们扩充 AgentMsg 消息如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"rust"},"content":[{"type":"text","text":"#[derive(Debug, Serialize, Deserialize, Clone)]\npub enum AgentMsg {\n LoginOk(u32, Player),\n LoginFail(String),\n Create(String),\n Notify(NotifyType)\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Create 使用暱称来创建角色,Notify 通知 客户端元宇宙的变化。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们在客户端增加下面的代码实现命令行的处理:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"rust"},"content":[{"type":"text","text":"let mut input = String::new();\nstdin().read_line(&mut input).unwrap();\nlet words: Vec = input.split_ascii_whitespace().collect();\nif words[0].eq_ignore_ascii_case(\"create\") {\n\t game.send(&AgentMsg::Create(words[1].into()));\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"将输入根据分隔符拆分为 &str,并使用 collect 收集为一个数组,可以看到 Rust 使用函数编程模式之后表达效率的极大提升,一行代码可以实现 C/C++需要数十行代码完成到功能。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"rust"},"content":[{"type":"text","text":"let words: Vec = input.split_ascii_whitespace().collect();","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这样在客户端执行 ","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f3/f3893b5b3200f3faa1a3d728475d2276.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"create 就可以 将创建角色的协议发送到服务器端。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们在服务端维持一个 token_id 的 tuple 保持 客户端的 状态","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"rust"},"content":[{"type":"text","text":"let mut token_id = (None, None);\nmsg = socket.recv() => {\n match msg {\n Some(Ok(Message::Binary(buf)))=> {\n let mut de = rmp_serde::Deserializer::from_read_ref(&buf);\n let agent_msg: AgentMsg = Deserialize::deserialize(&mut de).unwrap();\n if let Some(id) = token_id.1 {\n rpc::run(&agent_tx, AgentRequest::IDMsg(id, agent_msg));\n } else {\n rpc::run(&agent_tx, AgentRequest::TokenMsg(token_id.0.clone().unwrap(), agent_msg));\n }\n },\n Some(Err(e))=> {\n println!(\"error -> {:?}\", e);\n break;\n },\n _=> {}\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服务器保持 客户端 Websocket 的线程,从客户端接受到二进制的消息之后,首先使用 messagepack 解码,如果没有获得 id,则使用AgentRequest::TokenMsg 转发这个客户端消息到 agent 服务进行处理,如果已经获得了 id,则使用AgentRequest::IDMsg 转发到agent 服务处理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"服务器 agent 服务相关处理的代码如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"rust"},"content":[{"type":"text","text":"AgentRequest::Login(token, _tx)=> {\n if let Some((id, player)) = self.load(&token) {\n super::rpc::run(world, WorldRequest::Enter(id, player.pos.clone()));\n _tx.send(AgentMsg::LoginOk(id, player));\n self.id_txs.insert(id, _tx);\n } else {\n _tx.send(AgentMsg::LoginFail(\"token do not exist\".into()));\n self.token_txs.insert(token.clone(), _tx);\n }\n },\n AgentRequest::TokenMsg(token, msg)=> {\n match msg {\n AgentMsg::Create(nick)=> {\n let id = self.create(&token, &nick);\n if let Some(tx) = self.token_txs.remove(&token) {\n tx.send(AgentMsg::LoginOk(id, self.agents[id].clone().unwrap())).unwrap();\n self.id_txs.insert(id, tx);\n }\n },\n _=> {}\n }\n },","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果是登录消息,获得 ID 或者 token 并将 发送消息到客户端的 tx 存放到 token的HashMap或者 ID的 BTreeMap 中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果是创建角色,则将创建后的id 和角色信息发送到 客户端。","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章