agones.dev/agones@v1.54.0/sdks/rust/src/sdk.rs (about) 1 // Copyright 2018 Google LLC All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 use std::{env, time::Duration}; 16 use tonic::transport::Channel; 17 18 mod api { 19 tonic::include_proto!("agones.dev.sdk"); 20 } 21 22 use api::sdk_client::SdkClient; 23 pub use api::{ 24 game_server::{ 25 status::{PlayerStatus, Port}, 26 ObjectMeta, Spec, Status, 27 }, 28 GameServer, 29 }; 30 31 pub type WatchStream = tonic::Streaming<GameServer>; 32 33 use crate::{alpha::Alpha, beta::Beta, errors::Result}; 34 35 #[inline] 36 fn empty() -> api::Empty { 37 api::Empty {} 38 } 39 40 /// SDK is an instance of the Agones SDK 41 #[derive(Clone)] 42 pub struct Sdk { 43 client: SdkClient<Channel>, 44 alpha: Alpha, 45 beta: Beta, 46 } 47 48 impl Sdk { 49 /// Starts a new SDK instance, and connects to localhost on the port specified 50 /// or else falls back to the `AGONES_SDK_GRPC_PORT` environment variable, 51 /// or defaults to 9357. 52 /// 53 /// # Errors 54 /// 55 /// - The port specified in `AGONES_SDK_GRPC_PORT` can't be parsed as a `u16`. 56 /// - A connection cannot be established with an Agones SDK server 57 /// - The handshake takes longer than 30 seconds 58 pub async fn new(port: Option<u16>, keep_alive: Option<Duration>) -> Result<Self> { 59 let addr: http::Uri = format!( 60 "http://localhost:{}", 61 port.unwrap_or_else(|| { 62 env::var("AGONES_SDK_GRPC_PORT") 63 .ok() 64 .and_then(|s| s.parse().ok()) 65 .unwrap_or(9357) 66 }) 67 ) 68 .parse()?; 69 70 Self::new_internal(addr, keep_alive).await 71 } 72 73 pub async fn new_with_host( 74 host: Option<String>, 75 port: Option<u16>, 76 keep_alive: Option<Duration>, 77 ) -> Result<Self> { 78 let addr: http::Uri = format!( 79 "{}:{}", 80 host.unwrap_or_else(|| { 81 env::var("AGONES_SDK_GRPC_HOST") 82 .ok() 83 .and_then(|s| s.parse().ok()) 84 .unwrap_or("http://localhost".to_owned()) 85 }), 86 port.unwrap_or_else(|| { 87 env::var("AGONES_SDK_GRPC_PORT") 88 .ok() 89 .and_then(|s| s.parse().ok()) 90 .unwrap_or(9357) 91 }) 92 ) 93 .parse()?; 94 95 Self::new_internal(addr, keep_alive).await 96 } 97 98 async fn new_internal(addr: http::Uri, keep_alive: Option<Duration>) -> Result<Self> { 99 let builder = tonic::transport::channel::Channel::builder(addr) 100 .connect_timeout(Duration::from_secs(30)) 101 .keep_alive_timeout(keep_alive.unwrap_or_else(|| Duration::from_secs(30))); 102 103 // will only attempt to connect on first invocation, so won't exit straight away. 104 let channel = builder.connect_lazy(); 105 let mut client = SdkClient::new(channel.clone()); 106 let alpha = Alpha::new(channel.clone()); 107 let beta = Beta::new(channel); 108 109 tokio::time::timeout(Duration::from_secs(30), async { 110 let mut connect_interval = tokio::time::interval(Duration::from_millis(100)); 111 112 loop { 113 connect_interval.tick().await; 114 if client.get_game_server(empty()).await.is_ok() { 115 break; 116 } 117 } 118 }) 119 .await?; 120 121 Ok(Self { client, alpha, beta }) 122 } 123 124 /// Alpha returns the Alpha SDK 125 #[inline] 126 pub fn alpha(&self) -> &Alpha { 127 &self.alpha 128 } 129 130 /// Beta returns the Beta SDK 131 #[inline] 132 pub fn beta(&self) -> &Beta { 133 &self.beta 134 } 135 136 /// Marks the Game Server as ready to receive connections 137 pub async fn ready(&mut self) -> Result<()> { 138 Ok(self.client.ready(empty()).await.map(|_| ())?) 139 } 140 141 /// Allocate the Game Server 142 pub async fn allocate(&mut self) -> Result<()> { 143 Ok(self.client.allocate(empty()).await.map(|_| ())?) 144 } 145 146 /// Marks the Game Server as ready to shutdown 147 pub async fn shutdown(&mut self) -> Result<()> { 148 Ok(self.client.shutdown(empty()).await.map(|_| ())?) 149 } 150 151 /// Returns a [`tokio::sync::mpsc::Sender`] that will emit a health check 152 /// every time a message is sent on the channel. 153 pub fn health_check(&self) -> tokio::sync::mpsc::Sender<()> { 154 let mut health_client = self.clone(); 155 let (tx, mut rx) = tokio::sync::mpsc::channel(10); 156 157 tokio::task::spawn(async move { 158 let health_stream = async_stream::stream! { 159 while rx.recv().await.is_some() { 160 yield empty(); 161 } 162 }; 163 164 let _ = health_client.client.health(health_stream).await; 165 }); 166 167 tx 168 } 169 170 /// Set a Label value on the backing GameServer record that is stored in Kubernetes 171 pub async fn set_label( 172 &mut self, 173 key: impl Into<String>, 174 value: impl Into<String>, 175 ) -> Result<()> { 176 Ok(self 177 .client 178 .set_label(api::KeyValue { 179 key: key.into(), 180 value: value.into(), 181 }) 182 .await 183 .map(|_| ())?) 184 } 185 186 /// Set a Annotation value on the backing Gameserver record that is stored in Kubernetes 187 pub async fn set_annotation( 188 &mut self, 189 key: impl Into<String>, 190 value: impl Into<String>, 191 ) -> Result<()> { 192 Ok(self 193 .client 194 .set_annotation(api::KeyValue { 195 key: key.into(), 196 value: value.into(), 197 }) 198 .await 199 .map(|_| ())?) 200 } 201 202 /// Returns most of the backing GameServer configuration and Status 203 pub async fn get_gameserver(&mut self) -> Result<GameServer> { 204 Ok(self 205 .client 206 .get_game_server(empty()) 207 .await 208 .map(|res| res.into_inner())?) 209 } 210 211 /// Reserve marks the Game Server as Reserved for a given duration, at which point 212 /// it will return the GameServer to a Ready state. 213 /// Do note, the smallest unit available in the time.Duration argument is one second. 214 pub async fn reserve(&mut self, duration: Duration) -> Result<()> { 215 Ok(self 216 .client 217 .reserve(api::Duration { 218 seconds: std::cmp::max(duration.as_secs() as i64, 1), 219 }) 220 .await 221 .map(|_| ())?) 222 } 223 224 /// Watch the backing GameServer configuration on updated 225 pub async fn watch_gameserver(&mut self) -> Result<WatchStream> { 226 Ok(self 227 .client 228 .watch_game_server(empty()) 229 .await 230 .map(|stream| stream.into_inner())?) 231 } 232 }