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":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章